dojo.require("dijit._base.place");
dojo.require("dojo.string");

if (!Array.prototype.indexOf)
{
	Array.prototype.indexOf = function(elt /*, from*/)
	{
		var len = this.length;

		var from = Number(arguments[1]) || 0;
		from = (from < 0)
			? Math.ceil(from)
			: Math.floor(from);

		if (from < 0)
			from += len;

		for (; from < len; from++)
		{
			if (from in this && this[from] === elt)
				return from;
		}
		return -1;
	};
}

if (!Array.prototype.isValue)
{
	Array.prototype.hasValue = function(elt /*, from*/)
	{
		if(this.indexOf(elt) == -1)
			return false;
		return true;
	};
}

String.prototype.replaceAll = function(str_to_replace, replacement_str)
{
	var subject = this;
	if(str_to_replace.indexOf(replacement_str)>=0)
		return subject;

	//NOTE: Double loop prevents infinite recursion in cases like: some_string.replaceAll('\\', '\\\\')

	while(subject.indexOf(str_to_replace)>=0)
	{
		subject = subject.replace(str_to_replace, '[!_temp_!]');
	}

	while(subject.indexOf('[!_temp_!]')>=0)
	{
		subject = subject.replace('[!_temp_!]', replacement_str);
	}
	return subject;
}

String.prototype.trim = function()
{
	var subject = this;
	return subject.replace(/^\s+|\s+$/g, '');
}

String.prototype.extractStringsSurroundedBy = function(opening_char, closing_char)
{
	var found_string_array = new Array();
	
	var temp_haystack = this;
	while(temp_haystack.indexOf(opening_char) >= 0 && temp_haystack.indexOf(closing_char) >= 0 && temp_haystack.indexOf(opening_char) < temp_haystack.indexOf(closing_char))
	{
		// Extract the string
		var string_to_add = temp_haystack.substr(temp_haystack.indexOf(opening_char)+1, temp_haystack.indexOf(closing_char) - temp_haystack.indexOf(opening_char) - 1);
		
		// Add it to the array
		found_string_array.push(string_to_add);
		
		// Move on to the next part of the string
		temp_haystack = temp_haystack.substr(temp_haystack.indexOf(closing_char)+1);
	}
	
	return found_string_array;
}

/* This utility function resolves the string movieName to a Flash object reference based on browser type. */
function GetMovieObj(movieName)
{
	if (navigator.appName.indexOf("Microsoft") != -1)
	{
		return window[movieName];
	}
	else
	{
		return document[movieName];
	}
}


// ========================================================
// declare the JSFeature namespace
var JSFeature = {};
JSFeature.white_out_box_connect_handle_onscroll = null;
JSFeature.white_out_box_connect_handle_onresize = null;
JSFeature.center_on_screen_connect_handle_onscroll = null;
JSFeature.center_on_screen_connect_handle_onresize = null;
JSFeature.center_on_screen_node_array = new Array();
JSFeature.delayed_processor_data_obj_array = [];

// =============================================================================================
// AjaxURLHelper
// Note: the back button will not work in IE (bookmarks, etc, does still work).  
// In order to get this to work we need to implement an iframe technique explained 
// here: http://ajaxpatterns.org/Unique_URLs
//
// The problem is, doing that means a specific "dummy" file will always need to be available
// and thus global to all sites (because javascript uses paths relative to the page, not to
// where the js file resides on the server).
// ---------------------------------------------------------------------------------------------
JSFeature.AjaxUrlHelper = {};
JSFeature.AjaxUrlHelper.recent_window_location_hash = '';
JSFeature.AjaxUrlHelper.polling_timer_id = 0;
JSFeature.AjaxUrlHelper.AjaxURLInit = function()
{
}
JSFeature.AjaxUrlHelper.SetBrowserLocationHash = function(value)
{
	window.location.hash = value;
}
JSFeature.AjaxUrlHelper.GetBrowserLocationHash = function()
{
	return window.location.hash;
}
// starts continuously checking the window.location.hash for changes (for every count down of milliseconds).
// if a change is noticed, then the callback is ran with the new hash value as a parameter (and the polling stops).
JSFeature.AjaxUrlHelper.StartHashChangePolling = function(callback, milliseconds, is_ignore_current_hash)
{
	if(is_ignore_current_hash)
	{
		JSFeature.AjaxUrlHelper.recent_window_location_hash = JSFeature.AjaxUrlHelper.GetBrowserLocationHash();
	}
	
	if(!milliseconds)
	{
		milliseconds = 50;
	}
	
	if(JSFeature.AjaxUrlHelper.recent_window_location_hash != JSFeature.AjaxUrlHelper.GetBrowserLocationHash())
	{
		JSFeature.AjaxUrlHelper.recent_window_location_hash = JSFeature.AjaxUrlHelper.GetBrowserLocationHash();
		callback(JSFeature.AjaxUrlHelper.GetBrowserLocationHash());
	}
	else
	{
		JSFeature.AjaxUrlHelper.polling_timer_id = setTimeout(function(){JSFeature.AjaxUrlHelper.StartHashChangePolling(callback, milliseconds);}, milliseconds);
	}
}
JSFeature.AjaxUrlHelper.StopHashChangePolling = function()
{
	clearTimeout(JSFeature.AjaxUrlHelper.polling_timer_id);
}
// =============================================================================================


// =============================================================================================
// functions for helping with ajax and input
// ---------------------------------------------------------------------------------------------
JSFeature.SimpleAjaxTextPost = function(url, content, callback)
{
	dojo.xhrPost({
		url:url,
		content:content,
		handleAs: 'text',
		load: callback
	});
}
JSFeature.SimpleAjaxJsonPost = function(url, content, callback)
{
	dojo.xhrPost({
		url:url,
		content:content,
		handleAs: 'json',
		load: callback
	});
}
JSFeature.SimpleAjaxJsonFormPost = function(url, form_id, callback, option_obj)
{
	
	if(!option_obj || !option_obj.is_not_show_loading) 
		JSFeature.ShowLoadingOnNode(form_id);
	
	dojo.xhrPost({
		url:url,
		form:form_id,
		handleAs: 'json',
		load: function(response, params)
		{
			if(!option_obj || !option_obj.is_not_show_loading) 
				JSFeature.HideLoadingOnNode(form_id); 
			if(callback) 
				callback(response, params);
		}
	});
}

// *** finds a label tag associated with a node (usually an input node, etc)
JSFeature.FindLabelForNode = function(control_node)
{
	var idVal = control_node.id;
	labels = document.getElementsByTagName('label');
	for( var i = 0; i < labels.length; i++ ) 
	{
		if (labels[i].htmlFor == idVal)
			return labels[i];
	}

}

// *** finds all label tags and puts the node as a .label value in its associated control node (usually an input node, etc)
JSFeature.PutLabelsInControlNodes = function()
{
	var control_node;
	
	labels = document.getElementsByTagName('label');
	for( var i = 0; i < labels.length; i++ ) 
	{
		control_node = dojo.byId(labels[i].htmlFor);
		if(control_node)
		{
			control_node.label = labels[i];
		}
	}
}

JSFeature.SetInputErrorWithClassName = function(input_node, input_class, label_class)
{
	if(!input_node.is_error)
	{
		input_node.pre_error_className = input_node.className;
		input_node.className = input_class;
		input_node.is_error = true;
		var label_node = JSFeature.FindLabelForNode(input_node);
		
		if(label_node)
		{
			label_node.pre_error_className = label_node.className;
			label_node.className = label_class;
			label_node.is_error = true;
		}
	}
}

JSFeature.ClearInputErrorWithClassName = function(input_node)
{
	if(input_node.is_error)
	{
		input_node.className = input_node.pre_error_className;
		var label_node = JSFeature.FindLabelForNode(input_node);
		input_node.is_error = false;
		if(label_node)
		{
			label_node.className = label_node.pre_error_className;
			label_node.is_error = false;
		}
	}
}
JSFeature.SetInputErrorWithStyle = function(input_node, input_style, label_style)
{
	if(!input_node.is_error)
	{
		input_node.pre_error_style = input_node.style;
		input_node.style = input_style;
		input_node.is_error = true;
		var label_node = JSFeature.FindLabelForNode(input_node);
		
		if(label_node)
		{
			label_node.pre_error_style = label_node.style;
			label_node.style = label_style;
			label_node.is_error = false;
		}
	}
}

JSFeature.ClearInputErrorWithStyle = function(input_node)
{
	if(input_node.is_error)
	{
		input_node.style = input_node.pre_error_style;
		input_node.is_error = false;
		var label_node = JSFeature.FindLabelForNode(input_node);
		if(label_node)
		{
			label_node.style = label_node.pre_error_style;
			label_node.is_error = false;
		}
	}
}
// =============================================================================================


JSFeature.ParseXML = function(xml)
{
	if (window.DOMParser)
	{
		parser=new DOMParser();
		xmlDoc=parser.parseFromString(xml,"text/xml");
	}
	else // Internet Explorer
	{
		xmlDoc=new ActiveXObject("Microsoft.XMLDOM");
		xmlDoc.async="false";
		xmlDoc.loadXML(xml);
	} 
	
	return xmlDoc;
}


JSFeature.IsHTTPS = function()
{
	var loc = new String(window.parent.document.location);
	if(loc.indexOf("https://") != -1)
		return true;

	return false;
}


// Returns the version of Internet Explorer or a -1
// (indicating the use of another browser).
JSFeature.GetInternetExplorerVersion = function ()
{
	var rv = -1; // Return value assumes failure.
	if (navigator.appName == 'Microsoft Internet Explorer')
	{
		var ua = navigator.userAgent;
		var re  = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
		if (re.exec(ua) != null)
			rv = parseFloat( RegExp.$1 );
	}
	return rv;
}

// returns 0 for quirks mode, 1 for CSS1Comap, and 2 for unknown/semi-compliant
JSFeature.CheckCompatMode = function()
{
	if(document.compatMode)
	{
		var mode=document.compatMode;
		if(mode)
		{
			if(mode=='BackCompat')
			{
				//'Quirks'
				return 0;
			}
			else if(mode=='CSS1Compat')
			{
				//'Standards Compliance'
				return 1;
			}
			else
			{
				//'Almost Standards Compliance'
				return 2;
			}
		}
	}
	return 0;
}

JSFeature.GetBrowserInfoObj = function()
{
	var data = {};
	
	// grab the navigator obj
	data.navigator = navigator;
	
	// expand the navigator obj into separate fields for ease of use
	data.browser_code_name = navigator.appCodeName;
	data.browser_name = navigator.appName;
	data.browser_version = navigator.appVersion;
	data.browser_is_cookie_enabled = navigator.cookieEnabled;
	data.platform = navigator.platform;
	data.user_agent = navigator.userAgent;
	
	// get some extended browser/document data
	data.IE_browser_mode = JSFeature.GetInternetExplorerVersion();
	data.IE_document_mode = -1;
	data.compat_mode = -1;
	if(document.compatMode)
	{
		data.compat_mode = document.compatMode;
	}
	if(document.documentMode)
	{
		data.IE_document_mode = document.documentMode;
	}

	return data;
}


// =============================================================================================
// methods to help with making a "delayed repeated process" such as when something is typed
// into an input and we need to check something when they are done.
// ---------------------------------------------------------------------------------------------
JSFeature.DelayedRepeatedProcess_GetCreateDataObj = function(id)
{
	var i=0;
	var is_found = false;
	
	for(i=0; i<JSFeature.delayed_processor_data_obj_array.length; i++)
	{
		if(JSFeature.delayed_processor_data_obj_array[i].id == id)
		{
			return JSFeature.delayed_processor_data_obj_array[i];
		}
	}
	
	var tmp_obj = {};
	tmp_obj.id = id;
	tmp_obj.timeout_id = null;
	tmp_obj.is_request_pending = false;
	tmp_obj.is_request_in_progress = false;
	tmp_obj.timeout_callback = null;
	tmp_obj.pending_timeout_callback = null;
	tmp_obj.pending_delay_ms = null;
	JSFeature.delayed_processor_data_obj_array.push(tmp_obj);
	return tmp_obj;
}

JSFeature.DelayedRepeatedProcess_StartDelay = function(id, delay_ms, timeout_callback, force_restart)
{
	var data_obj = JSFeature.DelayedRepeatedProcess_GetCreateDataObj(id);
	
	if(data_obj.is_request_in_progress && !force_restart)
	{
		data_obj.is_request_pending = true;
		data_obj.pending_timeout_callback = timeout_callback;
		data_obj.pending_delay_ms = delay_ms;
	}
	else
	{
		data_obj.is_request_in_progress = false;
		data_obj.is_request_pending = false;
		data_obj.pending_timeout_callback = null
		
		if(data_obj.timeout_id)
		{
			clearTimeout(data_obj.timeout_id);
		}

		data_obj.timeout_callback = timeout_callback;

		data_obj.timeout_id = setTimeout(function() { JSFeature._DelayedRepeatedProcess_TimeoutEnded(id); }, delay_ms);
	}
}

JSFeature._DelayedRepeatedProcess_TimeoutEnded = function(id)
{
	var data_obj = JSFeature.DelayedRepeatedProcess_GetCreateDataObj(id);
	data_obj.is_request_in_progress = true;
	data_obj.timeout_callback();
}

JSFeature.DelayedRepeatedProcess_RequestReturn = function(id, on_end_callback, is_force_run_on_end_callback)
{
	var data_obj = JSFeature.DelayedRepeatedProcess_GetCreateDataObj(id);

	var _is_run_on_end_callback = true;
	
	data_obj.is_request_in_progress = false;
	
	if(data_obj.is_request_pending)
	{
		JSFeature.DelayedRepeatedProcess_StartDelay(data_obj.id, data_obj.pending_delay_ms, data_obj.pending_timeout_callback);
		_is_run_on_end_callback = is_force_run_on_end_callback?true:false;
	}


	if(_is_run_on_end_callback)
	{
		on_end_callback();
	}
	
	return _is_run_on_end_callback;
}
// =============================================================================================

// This funtion is an all-in-one way to get an action to occur based on a trigger
// that resets its timer everything the trigger occurs.  I.e., everytime a key
// is pressed, the action with occur in delay_ms time.  If a key is pressed before
// delay_ms has passed, then the delay is reset back to delay_ms.  This repeats
// until a key isn't pressed before the delay has passed.
// callback is called once the delay has passed.  Once the callback has started, if a key
// is pressed, the action (that leads to the callback) will not happen again until the
// InputDelayedAction_CallbackDone method is executed on the input_obj.  If no callback
// was ever defined, then InputDelayedAction_CallbackDone never needs to be ran.
JSFeature.InputDelayedAction = function(input_obj, delay_ms, callback)
{
	// *** 
	// some init
	// ***
	
	// *** set the timeout ended callback.
	if(!input_obj.InputDelayedAction_timeout_ended_callback)
	{
		input_obj.InputDelayedAction_timeout_ended_callback = function()
		{
			if(input_obj.InputDelayedAction_is_in_process)
			{
				clearTimeout(input_obj.InputDelayedAction_timeout_id);
				input_obj.InputDelayedAction_timeout_id = setTimeout(function(){input_obj.InputDelayedAction_timeout_ended_callback();}, delay_ms);
			}
			else
			{
				if(callback())
				{
					input_obj.InputDelayedAction_is_in_process = true;
				}
				else
				{
					input_obj.InputDelayedAction_is_in_process = false;
				}
			}
		};
	}
	
	// *** set up the callback done function
	if(!input_obj.InputDelayedAction_CallbackDone)
	{
		input_obj.InputDelayedAction_CallbackDone = function(){input_obj.InputDelayedAction_is_in_process = false};
	}
	
	// *** set up the clear function.
	if(!input_obj.InputDelayedAction_Clear)
	{
		input_obj.InputDelayedAction_Clear = function()
		{
			input_obj.InputDelayedAction_is_in_process = false;
			clearTimeout(input_obj.InputDelayedAction_timeout_id);
		};
	}

	// ***
	// timer logic
	// ***
	
	// *** (re)start timer 
	clearTimeout(input_obj.InputDelayedAction_timeout_id);
	input_obj.InputDelayedAction_timeout_id = setTimeout(function(){input_obj.InputDelayedAction_timeout_ended_callback();}, delay_ms);
}




JSFeature.Round = function(num, precision)
{
	var result;

	if(precision < 1)
	{
		result = Math.round(num);
	}
	else
	{
		result = Math.round(num*100000)/(100000);
		result = Math.round(num*10*precision)/(10*precision);
	}

	return result;
}

/**
*
*  Base64 encode / decode
*  http://www.webtoolkit.info/
*
**/

JSFeature.Base64 = {

	// private property
	_keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",

	// public method for encoding
	encode : function (input) {
		var output = "";
		var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
		var i = 0;

		input = JSFeature.Base64._utf8_encode(input);

		while (i < input.length) {

			chr1 = input.charCodeAt(i++);
			chr2 = input.charCodeAt(i++);
			chr3 = input.charCodeAt(i++);

			enc1 = chr1 >> 2;
			enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
			enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
			enc4 = chr3 & 63;

			if (isNaN(chr2)) {
				enc3 = enc4 = 64;
			} else if (isNaN(chr3)) {
				enc4 = 64;
			}

			output = output +
			this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
			this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);

		}

		return output;
	},

	// public method for decoding
	decode : function (input) {
		var output = "";
		var chr1, chr2, chr3;
		var enc1, enc2, enc3, enc4;
		var i = 0;

		input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");

		while (i < input.length) {

			enc1 = this._keyStr.indexOf(input.charAt(i++));
			enc2 = this._keyStr.indexOf(input.charAt(i++));
			enc3 = this._keyStr.indexOf(input.charAt(i++));
			enc4 = this._keyStr.indexOf(input.charAt(i++));

			chr1 = (enc1 << 2) | (enc2 >> 4);
			chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
			chr3 = ((enc3 & 3) << 6) | enc4;

			output = output + String.fromCharCode(chr1);

			if (enc3 != 64) {
				output = output + String.fromCharCode(chr2);
			}
			if (enc4 != 64) {
				output = output + String.fromCharCode(chr3);
			}

		}

		output = JSFeature.Base64._utf8_decode(output);

		return output;

	},

	// private method for UTF-8 encoding
	_utf8_encode : function (string) {
		string = string.replace(/\r\n/g,"\n");
		var utftext = "";

		for (var n = 0; n < string.length; n++) {

			var c = string.charCodeAt(n);

			if (c < 128) {
				utftext += String.fromCharCode(c);
			}
			else if((c > 127) && (c < 2048)) {
				utftext += String.fromCharCode((c >> 6) | 192);
				utftext += String.fromCharCode((c & 63) | 128);
			}
			else {
				utftext += String.fromCharCode((c >> 12) | 224);
				utftext += String.fromCharCode(((c >> 6) & 63) | 128);
				utftext += String.fromCharCode((c & 63) | 128);
			}

		}

		return utftext;
	},

	// private method for UTF-8 decoding
	_utf8_decode : function (utftext) {
		var string = "";
		var i = 0;
		var c = c1 = c2 = 0;

		while ( i < utftext.length ) {

			c = utftext.charCodeAt(i);

			if (c < 128) {
				string += String.fromCharCode(c);
				i++;
			}
			else if((c > 191) && (c < 224)) {
				c2 = utftext.charCodeAt(i+1);
				string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
				i += 2;
			}
			else {
				c2 = utftext.charCodeAt(i+1);
				c3 = utftext.charCodeAt(i+2);
				string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
				i += 3;
			}

		}

		return string;
	}

}

JSFeature.CreateCookie = function(name,value,days)
{
	if(days)
	{
		var date = new Date();
		date.setTime(date.getTime()+(days*24*60*60*1000));
		var expires = "; expires="+date.toGMTString();
	}
	else
		var expires = "";

	document.cookie = name+"="+value+expires+"; path=/";
}

JSFeature.ReadCookie = function(name)
{
	var nameEQ = name + "=";
	var ca = document.cookie.split(';');
	for(var i=0;i < ca.length;i++)
	{
		var c = ca[i];
		while (c.charAt(0)==' ')
			c = c.substring(1,c.length);
		if (c.indexOf(nameEQ) == 0)
			return c.substring(nameEQ.length,c.length);
	}
	return null;
}

JSFeature.EraseCookie = function(name)
{
	JSFeature.CreateCookie(name,"",-1);
}


JSFeature.HtmlEntities = function(text)
{
	text = text.replaceAll('"', '&quot;');
	text = text.replaceAll('<', '&lt;');

	return text;
}
JSFeature.IsValidEmailAddress = function(address)
{
	var reg = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/;
	if(reg.test(address) == false)
	{
		return false;
	}
	return true;
}
JSFeature.AppendNodeToBody = function(node)
{
	var body_tag = dojo.query('body')[0];
	body_tag.appendChild(node);
}
JSFeature.RemoveNode = function(node)
{
	//var body_tag = dojo.query('body')[0];
	if(node)
		node.parentNode.removeChild(node);
}

JSFeature.WhiteOutNode = function(id, opacity, color, z_index)
{
	var node = document.getElementById(id);
	if(node)
	{
		var bounding_box_obj = JSFeature.GetNodeBoundingBox(node);
		var use_node_id = 'local_node_white_out_'+id;
		
		if(!dojo.byId(use_node_id))
		{
			var new_div = document.createElement('div');

			// Append to the body
			JSFeature.AppendNodeToBody(new_div);// NOTE: appending to "new_div" throws off the positioning because the position of new_div may end up being relative to one of it's parents

			new_div.id 			= 'local_node_white_out_'+node.id;

			new_div.style.position 	= 'absolute';
			new_div.style.display 	= 'block';
			new_div.style.zIndex 	= node.style.zIndex + 1;
			new_div.style.left 		= bounding_box_obj.left + 'px';
			new_div.style.top 		= bounding_box_obj.top + 'px';
			new_div.style.width 	= bounding_box_obj.width + 'px';
			new_div.style.height 	= bounding_box_obj.height + 'px';

			dojo.style(new_div, "opacity", opacity ? opacity : .5);
			dojo.style(new_div, "backgroundColor", color ? color : "black");

			new_div.innerHTML = '';
		}
	}
}

JSFeature.UnWhiteOutNode = function(id)
{
	//var wo_obj = document.getElementById('local_node_white_out_'+id);
	//wo_obj.parentNode.removeChild(wo_obj);

	JSFeature.RemoveNode(document.getElementById('local_node_white_out_'+id));
}

JSFeature.CenterNodeOnNode = function(src_node, target_node)
{
	if(src_node && target_node)
	{
		var bounding_box_obj = JSFeature.GetNodeBoundingBox(target_node);

		//target_node.appendChild(src_node);
		JSFeature.AppendNodeToBody(src_node);

		src_node.style.position = 'absolute';
		src_node.style.zIndex = target_node.style.zIndex + 2;

		src_node.style.left = (bounding_box_obj.left + bounding_box_obj.width / 2 - src_node.offsetWidth / 2) + 'px';
		src_node.style.top = (bounding_box_obj.top + bounding_box_obj.height / 2 - src_node.offsetHeight / 2) + 'px';
	}
}

JSFeature.RemoveCenterNodeOnNode = function(target_node)
{
	JSFeature.RemoveNode(target_node);
}

JSFeature.ShowWhiteOut = function(opacity, color, z_index)
{
	JSFeature.HideWhiteOut();
	if(!JSFeature.white_out_box_connect_handle_onscroll)
	{
		JSFeature.white_out_box_connect_handle_onscroll = dojo.connect(window, "onscroll", JSFeature._AdjustWhiteOut);
	}
	if(!JSFeature.white_out_box_connect_handle_onresize)
	{
		JSFeature.white_out_box_connect_handle_onresize = dojo.connect(window, "onresize", JSFeature._AdjustWhiteOut);
	}
	var wo_obj = dojo.byId('feature_white_out');
	if(!wo_obj)
	{
		var wo_obj = document.createElement('div');
		wo_obj.id = 'feature_white_out';
		dojo.style(wo_obj, "opacity", opacity ? opacity : .5);
		dojo.style(wo_obj, "backgroundColor", color ? color : "black");
		wo_obj.style.display = 'block';
		wo_obj.style.position = 'absolute';
		wo_obj.style.zIndex = (z_index ? z_index : "1000");
		wo_obj.innerHTML = '';
		JSFeature.AppendNodeToBody(wo_obj);
	}

	JSFeature._AdjustWhiteOut();
}

JSFeature._AdjustWhiteOut = function(css_class_name)
{
	var wo_obj = dojo.byId('feature_white_out');
	if(wo_obj)
	{
		var vp_obj = dijit.getViewport();
		wo_obj.style.width = vp_obj.w + 'px';
		wo_obj.style.height = vp_obj.h + 'px';
		wo_obj.style.left = vp_obj.l + 'px';
		wo_obj.style.top = vp_obj.t + 'px';
	}
}
JSFeature.HideWhiteOut = function(css_class_name)
{
	var wo_obj = dojo.byId('feature_white_out');

	if(wo_obj)
	{
		if(JSFeature.white_out_box_connect_handle_onscroll)
		{
			dojo.disconnect(JSFeature.white_out_box_connect_handle_onscroll);
			JSFeature.white_out_box_connect_handle_onscroll = null;
		}

		if(JSFeature.white_out_box_connect_handle_onresize)
		{
			dojo.disconnect(JSFeature.white_out_box_connect_handle_onresize);
			JSFeature.white_out_box_connect_handle_onresize = null;
		}

		JSFeature.RemoveNode(wo_obj);
	}
}

JSFeature.CenterOnScreen = function(node)
{
	JSFeature.center_on_screen_node_array.push(node);

	if(!JSFeature.center_on_screen_connect_handle_onscroll)
	{
		JSFeature.center_on_screen_connect_handle_onscroll = dojo.connect(window, "onscroll", JSFeature._UpdateCenterOnScreen);
	}
	if(!JSFeature.center_on_screen_connect_handle_onresize)
	{
		JSFeature.center_on_screen_connect_handle_onresize = dojo.connect(window, "onresize", JSFeature._UpdateCenterOnScreen);
	}

	JSFeature._UpdateCenterOnScreen();
}

JSFeature.StopCenterOnScreen = function(node)
{
	try
	{
		var tmp_array = JSFeature.center_on_screen_node_array;
		JSFeature.center_on_screen_node_array = new Array();
		var i;
		var cur_node;
		for(i=0; i<tmp_array.length; i++)
		{
			cur_node = tmp_array[i];
			if(cur_node != node)
			{
				JSFeature.center_on_screen_node_array.push(cur_node);
			}
		}
	}
	catch(e)
	{
	}
}

JSFeature._UpdateCenterOnScreen = function()
{
	try
	{
		var i;
		var node, vp_obj, nw, nh, new_w, new_h;
		for(i=0; i<JSFeature.center_on_screen_node_array.length; i++)
		{
			node = JSFeature.center_on_screen_node_array[i];
			vp_obj = dijit.getViewport();
			nw = node.offsetWidth;
			nh = node.offsetHeight;

			new_w = (vp_obj.w/2 - nw/2) + vp_obj.l;
			new_h = (vp_obj.h/2 - nh/2) + vp_obj.t;

			node.style.left = new_w > 0?new_w + 'px':'0px';
			node.style.top = new_h > 0?new_h + 'px':'0px';
		}
	}
	catch(e)
	{
	}
}

JSFeature.GetNodePosArray = function(obj)
{
	var from_parent_only = false;//todo: Send this in as a param?

	var curleft = curtop = 0;
	if (obj && obj.offsetParent)
	{
		if(from_parent_only)
		{
			return [obj.offsetLeft, obj.offsetTop];
		}
		else
		{
			do
			{
				curleft += obj.offsetLeft;
				curtop += obj.offsetTop;
			} while (obj = obj.offsetParent);
		}

	}

	return [curleft,curtop];
}

// left, top, width, height
JSFeature.GetNodeBoundingBox = function(obj)
{
	var left = 0;
	var top = 0;
	var width = 0;
	var height = 0;
	var right = 0;
	var bottom = 0;

	if(obj)
	{
		var tmp_pos_array = JSFeature.GetNodePosArray(obj);

		left = tmp_pos_array[0];
		top = tmp_pos_array[1];
		width = obj.offsetWidth;
		height = obj.offsetHeight;
		right = left + width;
		bottom = top + height;
	}
	return {'left':left,'top':top,'width':width,'height':height,'right':right,'bottom':bottom};
}

JSFeature.ShowLoadingOnNode = function(id)
{
	JSFeature.WhiteOutNode(id);

	if(!dojo.byId(id+'_center_message'))
	{
		var tmp_div = document.createElement('div');
		tmp_div.id = id+'_center_message';
		tmp_div.innerHTML = 'Please wait...';
		tmp_div.style.backgroundColor = 'white';
		tmp_div.style.border = '5px solid black';
		tmp_div.style.fontWeight = 'bold';
		tmp_div.style.padding = '15px';
		JSFeature.CenterNodeOnNode(tmp_div, document.getElementById(id))
	}
}

JSFeature.HideLoadingOnNode = function(id)
{
	JSFeature.UnWhiteOutNode(id);
	JSFeature.RemoveCenterNodeOnNode(document.getElementById(id+'_center_message'))
}

JSFeature.GetCurrencyFormat = function(num)
{   
	if(isNaN(num) || num === '' || num === null)
		return '$0.00';
	else
		return ('$' + parseFloat(num).toFixed(2));
}

JSFeature.FireEvent = function(element,event)
{
	if (document.createEventObject)
	{
		// dispatch for IE
		var evt = document.createEventObject();
		return element.fireEvent('on'+event,evt)
	}
	else
	{
		// dispatch for firefox + others
		var evt = document.createEvent("HTMLEvents");
		evt.initEvent(event, true, true ); // event type,bubbling,cancelable
		return !element.dispatchEvent(evt);
	}
}

JSFeature.ReplaceNonAlphaNumeric = function(original_string, replace_string)
{
	return JSFeature.ReplaceUnspecifiedCharacters(original_string, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', replace_string);
}

JSFeature.ReplaceUnspecifiedCharacters = function(original_string, specified_char_string, replace_string, is_reverse)
{
	var new_string = '';
	var i;
	for(i=0; i < original_string.length; i++)
	{
		$specified_index = specified_char_string.indexOf(original_string[i]);
		
		if($specified_index == -1 && !is_reverse || $specified_index != -1 && is_reverse)
		{
			if(replace_string)
			{
				new_string = new_string + '' + replace_string;
			}
		}
		else
		{
			new_string += original_string[i];
		}
	}
	
	return new_string;
}

// ========================================================
// declare error box registry namespace
var TWErrorRegistry = {obj_reg:[]};
TWErrorRegistry.AddObj = function(obj)
{
	var i;
	for(i=0; i<this.obj_reg.length; i++)
	{
		if(this.obj_reg[i].id == obj.id)
		{
			this.obj_reg[i] = obj;
			return;
		}
	}

	this.obj_reg.push(obj);
}
TWErrorRegistry.GetObj = function(id)
{
	var i;
	for(i=0; i<this.obj_reg.length; i++)
	{
		if(this.obj_reg[i].id == id)
			return this.obj_reg[i];
	}
}
// declare the TWError class
function TWError(parent_node, id, css_id)
{
	this.id = id;
	this.css_id = css_id;
	this.title;
	this.message_array = new Array();
	this.custom_create_callback = null;
	this.parent_node = null;

	this.SetTitle('The following errors have occured:');
	this.SetParentNode(parent_node);
	this.Hide();
	TWErrorRegistry.AddObj(this);

}

TWError.prototype.SetParentNode = function(parent_node)
{
	this.parent_node = parent_node;
}

TWError.prototype._pri_DefaultHTML = function()
{
	var i;
	var output = '';
	output += 	'<div id="'+this.id+'_error_box" class="'+(this.css_id?this.css_id+'_':'')+'error_box" style="display:none;">';
	output += 		'<div id="'+this.id+'_error_box_hide_link_wrapper" class="'+(this.css_id?this.css_id+'_':'')+'error_box_hide_wrapper">';
	output += 			'<div id="'+this.id+'_error_box_hide_link" class="'+(this.css_id?this.css_id+'_':'')+'error_box_hide_link" onclick="TWErrorRegistry.GetObj(\''+this.id+'\').Hide();">';
	output += 				'[close]';
	output += 			'</div>';
	output += 		'</div>';
	output += 		'<div id="'+this.id+'_error_box_title_wrapper" class="'+(this.css_id?this.css_id+'_':'')+'error_title_wrapper">'
	output += 			'<div id="'+this.id+'_error_box_title" class="'+(this.css_id?this.css_id+'_':'')+'error_title">'+this.title+'</div>';
	output += 		'</div>'
	output += 		'<div id="'+this.id+'_error_box_message_wrapper" class="'+(this.css_id?this.css_id+'_':'')+'error_box_message_wrapper">'
	output += 			'<ul>';
	for(i=0; i<this.GetErrorMessageCount(); i++)
	{
		output += 			'<li id="'+this.id+'_error_box_message_'+(i+1)+'" class="'+(this.css_id?this.css_id+'_':'')+'error_box_message">'+this.message_array[i]+'</li>';
	}
	output += 			'</ul>';
	output += 		'</div>';
	output += 	'</div>';

	return output;
}

TWError.prototype.SetCustomCreateCallback = function(callback)
{
	this.custom_create_callback = callback;
}


TWError.prototype._pri_CreateHTML = function()
{
	if(this.custom_create_callback)
		return this.custom_create_callback(this);
	else
		return this._pri_DefaultHTML();
}

TWError.prototype.Create = function()
{
	if(this.parent_node)
	{
		this.parent_node.innerHTML = this._pri_CreateHTML();
	}
}
TWError.prototype.Delete = function()
{
	if(this.parent_node)
	{
		this.parent_node.innerHTML = '';
	}
}
TWError.prototype.Show = function()
{
	this.Create();

	if(this.parent_node)
		this.parent_node.style.display = '';

	if(document.getElementById(''+this.id+'_error_box'))
	{
		document.getElementById(''+this.id+'_error_box').style.display = '';
	}
}

TWError.prototype.Hide = function()
{
	this.Create();

	if(this.parent_node)
		this.parent_node.style.display = 'none';

	if(document.getElementById(''+this.id+'_error_box'))
	{
		document.getElementById(''+this.id+'_error_box').style.display = 'none';
	}
}

TWError.prototype.SetTitle = function(title)
{
	this.title = title;
	this.Create();

}

TWError.prototype.SetMessageArray = function(message_array)
{
	this.message_array = message_array;
	this.Create();
}
TWError.prototype.AddMessage = function(message)
{
	this.message_array.push(message);
	this.Create();
}
TWError.prototype.ClearMessageArray = function(message)
{
	this.message_array = new Array();
	this.Create();
}
TWError.prototype.GetErrorMessageCount = function()
{
	if(this.message_array)
		return this.message_array.length;
	return 0;
}
