﻿(function($)
{
	var window = this, undefined, 
	Js = window.Js =
	{
		extend: function(oObject)
		{/* Extends the jQuery object throught Js. This allows for documentation of the Js Extensions.
			Internally this calls jQuery.extend(oObject);
			@Parameters
			*oObject (Object, Function): The object or function to extend jQuery with.
			*/		
			if(!oObject) { return; }	
			jQuery.extend(oObject);			
			jQuery.extend(true, Js, oObject);	
		},
		extendFn: function(oObject)
		{/* Extends the jQuery element through Js. This allows for documentation of the Js Extensions.
			Internally this calls jQuery.fn.extend(oObject);
			@Parameters
			*oObject (Object, Function): The object or function to extend the jQuery element.
			*/
			if(!oObject) { return; }
			jQuery.fn.extend(oObject);			
			jQuery.extend(true, Js.fn, oObject);
		},
		fn: {}
	};
	
	jQuery(function()
	{
		// scroll to the top of the page
		try { scrollTo(0, 1); } catch(e) {}
		
		$("body").keydown(function(event)
		{
			if($.KeyCode(event)!=112) { return; }
			return _OpenContextHelp(event);
		});
		
		// set up handling ajax errors
		jQuery.ajaxSetup(
		{
			error: function(oXMLHttpRequest, sStatus, oError)
			{	// may want a register ajax error function?
				// jch - could display this in debug mode only
				$.AlertUser("There was an error with an Ajax Call:\n<div class=\"ui-widget-content\" style=\"width:100%;height:200px;overflow:auto;\">"+oXMLHttpRequest.responseText+"</div>");	
			}
		});
		
		// jch - setup masking definitions
		$.extend($.mask.definitions, {"1":"[0-1]","2":"[0-2]","3":"[0-3]","4":"[0-4]","5":"[0-5]",
			"6":"[0-6]","7":"[0-7]","8":"[0-8]"});
	
		_PrepareForm(null);
	});
	
	Js.extend
	({
		SubmitEvent: function(sTarget, sType, oArgument, oElement)
		{/* Submits a form event.
			If oElement is inside an ajax form then an ajax event will be submitted.
			@Parameters
			*sTarget (String): The element that sent or is to recieve the event.
			*sType (String): The type of event. "Clicked", "Changed", etc.
			*oArgument (Object, String): An argument to send along with the event.
			*oElement (HTML Element): The element that fired the event. Can be null unless submitting an event within an Ajax container.	
			*/
			// see if we are in an ajax form
			var sArgument = (typeof oArgument == "string") ? oArgument : JSON.ToString(oArgument);		
			var oAjxForm = $(oElement).closest("[ajaxcontainerid]");

			if(oAjxForm.length == 0)
			{	// see if we are in a dialog (buttons are outside the form)
				oAjxForm = $(oElement).closest(".ui-dialog");
				if(oAjxForm.length > 0) { oAjxForm = $(oAjxForm).find("form[url]"); }
			}
			
			if(oAjxForm.length > 0)
			{
				if($(oElement).attr("tagName")!="FORM")
				{
					var $El = $(oElement);
					var nScrollTop = $El.scrollTop();
					$.User.sContextData("LastFormTarget", {id:$(oElement).attr("id"),scrollTop:nScrollTop});
					//console.log("jQuery.SubmitEvent: nScrollTop: %d", nScrollTop);
					//jch - only handles form scroll position, any other inner elements may not be scrolled
				}
				//console.info("call:SubmitEvent(%s, %s, %s) ajax event for %s", sTarget, sType, oArgument, $(oAjxForm).attr("ajaxcontainerid"));
				return _SubmitAjaxEvent(sTarget, sType, sArgument, $(oAjxForm).attr("ajaxcontainerid"), oElement);
			}
			
			// submit the normal page event
			//console.info("call:SubmitEvent(%s, %s, %s) form event for %s", sTarget, sType, oArgument, $(oElement).attr("id"));
			try { event.stopPropagation() } catch(e){};
			var oForm = document.forms[0];
			oForm["__EVENTTARGET"].value = sTarget;
			oForm["__EVENTTYPE"].value = sType;
			oForm["__EVENTARGUMENT"].value = String(sArgument);
			oForm.submit()
		},

		AlertUser: function(sMessage, oOptions)
		{/*	Alerts the user using a jQuery dialog.
			@Parameters
			*sMessage (String): The message to alert.
			*oOptions (Object): A set of key value pairs for defining additional alert options.
			@oOptions
			*$Context (jQuery Element): The context that the alert occurs. Used for targeting specific controls.
			*oOnCloseFn (Function()): A callback which is executed when the dialog is closed.
			@Function AlertUser ( oMessages, oOptions )
			@Parameters
			*oMessages (Array): An array of message object containing the message and control names/values.
			@oMessages
			*sMessage (String): The message to alert.			
			*sControlName (String): The name of a form field that caused the alert.
			*sControlValue (String): Optional to target a control that has multiple instances.
			*/
			var fnClose = function()
			{	// attempt to focus the control and make the label red		
				$Dialog.dialog("destroy").remove();
				var oMsg = oMessages[0];
				if(oMsg.sControlName != null)
				{
					var $Control;
					if(oMsg.sControlValue != null) { $Control = $("[name="+oMsg.sControlName+"][value="+oMsg.sControlValue+"]", oOptions.$Context); }
					else { $Control = $("[name="+oMsg.sControlName+"]", oOptions.$Context); }					
					$Control.trigger("focus").trigger("select");
				}
				if(oOptions && oOptions.oOnCloseFn) { oOptions.oOnCloseFn(); }
			};
			
			var oMessages;
			if(typeof sMessage == "string") { oMessages = [{sMessage:sMessage}]; }
			else { oMessages = arguments[0]; }			
			
			var oMessage = [];
			$.each(oMessages, function(i, oMsg)
			{
				var sMsg = oMsg.sMessage;				
				if(oMsg.sControlName)
				{
					sMsg = sMsg.replace(/%Label/g, _GetLabel(oMsg.sControlName, oOptions.$Context));
					$("label[for='"+oMsg.sControlName+"']", oOptions.$Context).css("color", "red");
				}
				sMsg = sMsg.split("\n");
				sMsg[0] = "<b>"+sMsg[0]+"</b>";
				if(i > 0) oMessage.push("<p/>");
				oMessage.push(sMsg.join("\n").replace(/\n/g, "<br>"));				
			});
			
			var $Dialog = $("#js-form-alert");
			if($Dialog.length==0)
			{
				$("body").after("<div id=\"js-form-alert\"></div>");
				$Dialog = $("#js-form-alert");
			}
			$Dialog.html(oMessage.join(""));			
			$Dialog.dialog
			({
				modal:true,					width: 450,
				buttons: { "OK": fnClose }, close: fnClose
			});
			setTimeout(function(){ $(".ui-dialog-buttonpane button").trigger("focus"); }, 10);
		},
		
		OpenConfirmDialog: function(sName, sText, oButtons, sAjaxContainerID)
		{/*	Opens an inline dialog with the specified text and buttons
			@Parameters
			*sName (String): A name for the dialog. Used to target the "Closed" event.
			*sText (String, Function): Text to be displayed on the dialog.
			*oButtons (Object, Function): A set of key value pairs where the key is the button text and the value is the button argument.
			*sAjaxContainerID (String): The id of the ajax container if targeting an ajax dialog. If null, a full page post will occur.
			*/
			var fnClose = function(){$Dialog.dialog("destroy").remove();}
			var $Dialog = $("#js-form-confirm");
			if($Dialog.length==0)
			{
				$("body").after("<div id=\"js-form-confirm\"></div>");
				$Dialog = $("#js-form-confirm");
			}
			var buttons = [];
			var oAjaxForm = $("#"+sAjaxContainerID+" form");			
			$.each(oButtons, function(sButtonText, oArgument)
			{
				if(oArgument===false){buttons.push({text:sButtonText,fn:fnClose}); }
				else { buttons.push({text:sButtonText,fn:function() { fnClose(); $.SubmitEvent(sName, "Closed", oArgument, oAjaxForm) }});}
			});
			buttons.reverse(); oButtons = {}; // revers and recreate since the dialog floats/reverses the order
			$.each(buttons, function(){oButtons[this.text]=this.fn;});
			$Dialog.html(sText.replace(/\n/g, "<br>"));	
			$Dialog.dialog({modal:true,width: 450, buttons:oButtons, close:fnClose});
			setTimeout(function(){ $(".ui-dialog-buttonpane button:first").trigger("focus"); }, 10);
		},
		
		OpenAjaxDialog: function(oAjaxOptions)
		{/* Client side way of opening an ajax form.
			@Parameters
			*oAjaxOptions (Object): The standard set of Ajax options.
			*/
			_ExecuteOpenAjaxDialog(oAjaxOptions)
		},
		
		BindScript: function(sName, oFn)
		{/*	To be used by scripts that can be loaded within an ajax form.
			Use this in place of the jQuery.ready function. More commonly used as <code>jQuery(function(){...});</code>.
			Since anonymous functions are only loaded once, the BindScript call should register the function inside the anonymous
			function and then get referenced outside of the anonymous function as well. See example below.
			@Parameters
			*sName (String): The name of the script to store (if oFn is not null), or to execute (if oFn is null).
			*oFn (Function($BindEl)): The function to call when the document is ready or when the ajax data loads.
			The callback will have one argument which is the container that is being binded so targeting sub elements in the container is possible.
			<pre>
			jQuery.BindScript("js-ascript"); // called when the script is included
			(function($)
			{
				$.BindScript("js-ascript", function($BindEl)
				{
					// called when the document is ready
				});
			})(jQuery)			
			</pre>
			*/
			if(oFn) { _oBoundScriptHandlers[sName] = oFn; }
			if(!_bOkToBindScript && oFn) { jQuery(function() { oFn($("#Js_Form")); }); }
		},
		
		ClientRequestHandler: function(sRequest, oFn)
		{/*	Allows client scripts to expose functions to the server.
			@Parameters
			*sRequest (String): The name of the script/request. i.e. "js-form.progressbar".
			*oFn (Function($El, oOptions): The callback to be executed when the request is made.
			The jQuery element and options argument are passed from the Server.
			*/			
			_oClientRequestHandlers[sRequest] = oFn;			
		},	
		
		ServerData: function(sName, $Context)
		{/* Retrieves all data from a server tag with the specified name attribute then removes the server data tag.
			The return array will never be null. Test for length=0.
			@Returns Array
			@Parameters
			*sName (String): The name of the server data set in Js.Form.ServerData().
			*$Context (jQuery Element): The context to look for the server tag in. Can be null.
			*/
			var oSD = [];		
			$("[name="+sName+"]", $Context).each(function() // jch - using "server[name=]" does not work in opera
			{
				var $this = $(this);
				oSD.push(JSON.FromString(decodeURIComponent($this.attr("serverdata"))));				
				$this.remove();
			});			
			return oSD;
		},
		
		MapClientPath: function(sPath)
		{/*	A function to handle adjusting paths of client files within include files.
			See the server side function to set up the path mappings.
			@Paramters
			*sPath (String): A client path to map according to the client path rules set in Js.Form.MapClientPath(oRules).
			*/
			//console.info("start:MapClientPath(%s)", sPath);
			var oMap = _oClientPathMap || $.ServerData("Js.Form.ClientMappings");			
			if(oMap == null) { return sPath; }
			_oClientPathMap = oMap;
			oMap = oMap[0] || {};
			$.each(oMap, function(sName, oValue)
			{	
				//console.log("mapping from '%s' to '%s'", sName, oValue);			
				if(String(sPath).indexOf(sName)==0)
				{								
					sPath = sPath.replace(sName, oValue);
					return false;
				}
			});
			//console.info("end:MapClientPath(%s)", sPath);			
			return sPath;
		},
		
		Window: function(sName, sUrl)
		{/*	Opens a new browser window.
			@Parameters
			*sName (String): The name of the window. Can be null.
			*sUrl (String): The url of the window to open.
			@Function Window ( fnOptionsCallback )
			*fnOptionsCallback (Function(sName, sUrl, sDefaultOptions)): A function called to get customized window options.
			The default window options are:
			"resizable=1,toolbars=0,location=1,status=1,scrollbars=1,height=450,width=700"
			*/
			if($.isFunction(sName)) { _fnWindowOpts = sName; return; }			
			var sOpts = "resizable=1,toolbars=0,location=1,status=1,scrollbars=1,height=450,width=700"; // jch could use percent of doc width/height
			if(_fnWindowOpts) { sOpts = _fnWindowOpts(sName, sUrl, sOpts); }
			var oWin = window.open(sUrl, sName||"_self", sOpts);
			if(oWin == null) { alert("Popups blocked?"); } // jch could load url in an ajax container?
			else { oWin.focus(); }
		},	
		
		KeyCode: function(oEvent)
		{/* A cross browser way of getting the key code from the browser event object.
			For special keys you can test for oEvent.ctrlKey, oEvent.shiftKey, oEvent.altKey.
			@Returns Number
			@Parameters
			*oEvent (HTML Event Object): The event that contains the key code. Required.
			*/
			return oEvent.charCode || oEvent.keyCode || 0;
		},
		
		IsRightClick: function(oEvent)
		{/*	A way to tell if a click event was caused by the right mouse button.
			@Returns Boolean
			*/
			var e = oEvent;
			if (!e) { e = window.event; }
			if (e.keyCode) { code = e.keyCode; }
			else if (e.which) { code = e.which; }
			return (code==3);
		}
	});
	
	Js.extendFn
	({
		ServerData: function(sName)
		{/* Retrieves the encoded JSON data on the first jQuery element.
			@Returns Object
			@Parameters
			*sName (String): If specified, will return only the property with the specified name. Otherwise, the full object will be returned.
			*/
			var oServerData = JSON.FromString(decodeURIComponent(this.attr("serverdata")));
			if(sName && oServerData) { return oServerData[sName]; }
			return oServerData;
		},
		
		Value: function()
		{/*	Returns the value of a form field whose name is the same as the jQuery element id.
			If no elements are found, null will be returned.
			If two or more elements are found, the value will be a comma seperated string of values.
			@Function Value (sValue)
			@Returns jQuery
			*sValue (String, Array): The value to set. If an array, it will be converted to a comma seperated string.
			All fields whose name as the same as the jQuery element id will have the value atribute set to sValue.			
			*/
			if(arguments[0]!==undefined)
			{
				var sValue = $.isArray(arguments[0]) ? arguments[0].join(", ") : arguments[0];
				$("[name="+this.attr("id")+"]").attr("value", sValue);
				return this;
			}
			var oValues = [];
			var sName = this.attr("id");
			$("[name="+this.attr("id")+"]").each(function(){oValues.push($(this).attr("value"));});
			if(oValues.length==0){return null;}
			if(oValues.length==1){return oValues[0];}
			return oValues.join(", ");
		},
		
		Values: function()
		{/*	Convenience method to return multi value form controls as an array.
			*/
			if(this.IsEmpty()) { return []; }
			return this.Value().split(", ");			
		},
		
		IsEmpty: function()
		{/*	Returns true if the value returned from $.Value() is null or an empty string.
			*/
			var value = this.Value();
			return (value==null||$.trim(value)=="") ? true : false;
		},
		
		Disable: function()
		{/*	Disables the jQuery element(s) and adds the "ui-state-disabled" class.
			@Returns jQuery
			@Function Disable ( bDisable )
			@Parameters
			*bDisable (Boolean): If false, this will enable the element.
			*/
			var bDisable = arguments[0];
			this.each(function()
			{	
				if(bDisable===undefined) { $(this).trigger("blur").attr("disabled", "disabled").addClass("ui-state-disabled"); }
				else { bDisable ? $(this).Disable() : $(this).Enable() };
			});
			return this;
		},
		
		Enable: function()
		{/*	Enables the jQuery element(s) and removes the "ui-state-disabled" class.
			@Returns jQuery
			@Function Enable ( bEnable )
			@Parameters
			*bEnable (Boolean): If false, this will disable the element.
			*/
			var bEnable = arguments[0];	
			this.each(function()
			{			
				if(bEnable===undefined) { $(this).removeAttr("disabled").removeClass("ui-state-disabled"); }
				else { bEnable ? $(this).Disable() : $(this).Enable() };
			});
			return this;		
		}		
	});
	
	$.fn.AjaxContainer = function(oOptions)
	{/* Provides various methods on an Ajax Container.
		@Returns jQuery element
		@oOptions
		*event (jQuery Event): The normalized jquery event. May be used by methods to track the event target.
		*oAjaxOptions (Object): Extends the current ajax options associated with the container.
		*oUpdate (Object): Setting this will cause the ajax container to post an update with this object sent as the oEventArgs. Setting this to true will cause a simple refresh.
		*bPause (Boolean): If set to true, this will pause refreshing. To start refreshing again, call this method again with bStartRefresh set to true.
		*bStartRefresh (Boolean): If set to true, causes a refresh of the container at the nRefreshInterval ajax option. Hidden containers do not refresh, they check visibility on the specified interval and if visible, they will refresh.
		*bInit (Boolean): If set to true, will set the container up like a standard AjaxContainer and will set bLoad to true. To be used if an external script loads a form via ajax and needs to initialize the form. To be called after the ajax post.
		*bLoad (Boolean): Similar to bInit, however, this assumes the container has already been set up.
		@Note
		AjaxContainers have two jQuery data attributes.
		*oAjaxData (Object): An object with oAjaxOptions, sPreviousHtml, bIsPaused, and sFormData (serialized form data) attributes.
		*oAjaxHistory (Array): An array of previous oAjaxData items.
		oAjaxData can somtimes have additional runtime properties as well.
		@Bind "afterpost"
		Ajax containers can bind one event: "afterpost". Returning true from this function will stop html replacement.
		<code>$("#Element").bind("afterpost") { function(event, oEventArgs) { return true; } }</code>
		oEventArgs will have one property: "sResponse" which will contain the raw http response.
		Also, oEventArgs will have one method: "Response()" this will convert the raw http response to an object using JSON.
		*/
		var $AjaxEl = $(this);
		var sAjaxContainerID = $AjaxEl.attr("id");
		var oAjaxData = $AjaxEl.data("oAjaxData") || {};
		if(oOptions.oAjaxOptions)
		{
			oAjaxData.oAjaxOptions = $.extend(oAjaxData.oAjaxOptions, oOptions.oAjaxOptions);
			$AjaxEl.data("oAjaxData", oAjaxData);
		}
		if(oOptions.bInit)
		{
			oAjaxData.sPreviousHtml = $AjaxEl.html();
			$AjaxEl.data("oAjaxData", oAjaxData);
			oOptions.bLoad=true;
		}
		if(oOptions.bLoad)
		{
			//console.info("$.fn.AjaxContainer(): oOptions.bLoad: %s", sAjaxContainerID);
			if(oAjaxData.oAjaxOptions)
			{
				if(oAjaxData.oAjaxOptions.bSlideIn) { $AjaxEl.slideDown(); }
				if(oAjaxData.oAjaxOptions.bFadeIn) { $AjaxEl.fadeIn(); }			
				if(oAjaxData.oAjaxOptions.nRefreshInterval) { oOptions.bStartRefresh = true; }
			}
			_InitAjaxForm($AjaxEl);
		}
		if(oOptions.bPause)
		{
			//console.info("$.fn.AjaxContainer(): oOptions.bPause: %s", sAjaxContainerID);
			clearTimeout(oAjaxData.oAjaxOptions.nTimeOutID);
			oAjaxData.bPaused = true;
			$AjaxEl.data("oAjaxData", oAjaxData);
		}
		if(oOptions.bStartRefresh)
		{
			if(!oAjaxData.oAjaxOptions) { return; } // incase refreshing but removed
			clearTimeout(oAjaxData.oAjaxOptions.nTimeOutID);
			oAjaxData.bPaused = false;
			$AjaxEl.data("oAjaxData", oAjaxData);
			if(!oAjaxData.oAjaxOptions.nRefreshInterval) { return; }
			oAjaxData.oAjaxOptions.nTimeOutID = setTimeout(function()
			{
				if(oAjaxData.bPaused == true) { return; }
				if(oAjaxData.oAjaxOptions.bRefreshWhileHidden!==true && $AjaxEl.is(":visible")==false) // if hidden, do not submit, just wait another refresh interval
				{					
					oAjaxData.oAjaxOptions.nTimeOutID = setTimeout(function()
					{
						$AjaxEl.AjaxContainer({bStartRefresh:true});
					}, oAjaxData.oAjaxOptions.nRefreshInterval*1000);
					return;
				}
				$.SubmitEvent("Form", "Posted", "Refreshed", $("#"+sAjaxContainerID+" form"));
				//_SubmitAjaxEvent("Form", "Posted", "Refreshed", sAjaxContainerID);
			}, oAjaxData.oAjaxOptions.nRefreshInterval*1000);
		}
		if (oOptions.oUpdate)
		{
			if(oAjaxData.oAjaxOptions) { clearTimeout(oAjaxData.oAjaxOptions.nTimeOutID); }
			var $Element = oOptions.event ? $(oOptions.event.target) : $("form", $AjaxEl);
			$.SubmitEvent("Form", "Posted", oOptions.oUpdate, $Element);
		}
		return $AjaxEl;
	};
	
	// private functions and variables //
	var _oBoundScriptHandlers = {};
	var _oClientRequestHandlers = {};
	var _bOkToBindScript = false;
	var _fnWindowOpts;
	var _oClientPathMap;
	
	$.ClientRequestHandler("Js.Form.AjaxContainer", function($El, oOptions)
	{	
		$El.AjaxContainer(oOptions);
	});
	
	var _nRefresh = 0;
	var _SubmitAjaxEvent = function(sTarget, sType, sArgument, sAjaxContainerID, oElement)
	{/* Used to submit an event within an Ajax container.
		oElement can also be the id of the ajax container.
		*/
		if(sAjaxContainerID == null) { throw new Error("_SubmitAjaxEvent(): An ajax container id has not been defined"); }
		$AjaxEl = $("#"+sAjaxContainerID);
		var sAjaxUrl = decodeURI($("form", $AjaxEl).attr("url"));
		if(sAjaxUrl===undefined || sAjaxUrl=="undefined") { return; } // form has not yet loaded
		
		// hide the inner labels so they do not get added to the form data
		$.Watermark.HideAll();
		
		// disable sub form elements (for ie) so they do not get posted along with the primary form
		$("form form :input", $AjaxEl).attr("disabled", true);
		
		// store off the form data for later retrieval in case showing a different dialog
		var sFormData = $("form", $AjaxEl).serialize();
		
		var oAjaxData = $AjaxEl.data("oAjaxData");
		if(!oAjaxData) { return; }// container has been removed and setTimeout refresh has called for submit
		oAjaxData["sFormData"] = sFormData;
		$AjaxEl.data("oAjaxData", oAjaxData);
		$.Watermark.ShowAll();
				
		var sFormData = [sFormData,"&1=1",
			"&__EVENTTARGET=",encodeURIComponent(sTarget),
			"&__EVENTTYPE=",encodeURIComponent(sType),
			"&__EVENTARGUMENT=",encodeURIComponent(sArgument),
			"&__AJAXCONTAINERID=",sAjaxContainerID].join("");
		_AjaxPost(sAjaxUrl, sFormData, sAjaxContainerID);
		
		// disable the element if not a form
		var $Element = $(oElement);
		if($Element.attr("tagName") && $Element.attr("tagName").toLowerCase()!="form") { $Element.Disable(); }
	};
	
	var _PrepareForm = function($AjaxEl)
	{
		// apply the button styles
		$(".js-form-button, .js-form-cssbutton", $AjaxEl)
			.addClass("ui-state-default ui-corner-all")
			.mouseover(function(){$(this).addClass('ui-state-hover').removeClass('ui-state-default'); })
			.mouseout(function(){$(this).addClass('ui-state-default').removeClass('ui-state-hover'); })			
			.focus(function() { $(this).addClass('ui-state-focus').removeClass('ui-state-default');  })
			.blur(function() { $(this).removeClass('ui-state-focus').addClass('ui-state-default'); });
			
		_ApplyCollapsibleHeaders($AjaxEl);
		
		// do not submit any forms using form.submit()
		$("form", $AjaxEl).submit(function() { return false; });
		
		// inner labels
		$(":text[innerlabel]", $AjaxEl).each(function() { $(this).Watermark($(this).attr("innerlabel")); });
		
		// masks
		$(":text[mask]", $AjaxEl).each(function() { $(this).mask($(this).attr("mask"),{placeholder:" "}); });
		
		// set the focus and scroll position if needed
		var oLastFormTarget = $.User.sContextData("LastFormTarget")
		var $NextFocus = oLastFormTarget ? $("#"+oLastFormTarget.id, $AjaxEl) : [];
		if($NextFocus.length==0 && ($AjaxEl==null || $AjaxEl.attr("openajaxdialog")!="true"))
		{	// only focus the first input on the main form or inline dialog form
			var oInput = $(":text", $AjaxEl)[0]; // first input
			if(oInput) { $NextFocus = $(oInput); }			
			if($NextFocus.length==0 || $NextFocus.attr("innerlabel") || $NextFocus.attr("readonly")) { $NextFocus = null; }
		}
		if($NextFocus && $NextFocus.length > 0)
		{
			setTimeout(function()
			{
				$NextFocus.trigger("focus");
				if(oLastFormTarget && oLastFormTarget.scrollTop) { $NextFocus.scrollTop(oLastFormTarget.scrollTop); }
			}, 1);
		}
		
		
		// add the accelerators
		$("[accesskey]", $AjaxEl).each(function()
		{
			var oElement = $(this);
			var sHtml = oElement.html();
			var sKey = oElement.attr("accesskey");
			sHtml = sHtml.replace(sKey, "<u>"+sKey+"</u>");
			oElement.html(sHtml);
			
			// check for a label
			var oLabel = $("label[for="+oElement.attr("name")+"]");
			if(oLabel.html())
			{	
				var sHtml = oLabel.html();
				sHtml = sHtml.replace(sKey, "<u>"+sKey+"</u>");
				oLabel.html(sHtml);
			}			
		});
		
		// attach events
		$("[event]", $AjaxEl).each(function()
		{
			var $this = $(this);
			var oEvent = JSON.FromString(decodeURIComponent($this.attr("event")));
			if(String(oEvent.sClientEvent).toLowerCase() == "change")
			{
				$this.change(function() { jQuery.SubmitEvent(oEvent.sTarget, oEvent.sType, oEvent.oArgument, $this); });
				return;
			}
			$this.click(function(event)
			{
				switch(oEvent.sType.toLowerCase())
				{
					case "openhelpwindow": _OpenContextHelp(event, oEvent.sHelpWindowUrl); break;
					case "openwindow": $.Window(oEvent.sWindowName, oEvent.sWindowUrl); break;
					case "openajaxdialog": _ExecuteOpenAjaxDialog(oEvent.oAjaxOptions); break;					
					default: $.SubmitEvent(oEvent.sTarget, oEvent.sType, oEvent.oArgument, $this);			
				}
			});
		});	
		
		// button click on enter / click on esc
		$(":input", $AjaxEl).keydown(function(oEvent)
		{
			if($(this).attr("type")=="textarea") { return; }
			var nKeyCode = $.KeyCode(oEvent);
			if(nKeyCode == 10 || nKeyCode == 13)
			{
				var oEls = $("[clickOnEnter]", $AjaxEl);
				if(oEls.length != 0) { oEls.trigger("click"); }
				oEvent.preventDefault();
				oEvent.stopPropagation();				
				return false;
			}
			else if(nKeyCode == 27)
			{
				$("[clickOnEsc]", $AjaxEl).trigger("click");
				oEvent.stopPropagation();				
				return false;
			}
		});
		
		// open inner ajax dialogs
		var oServerData = $.ServerData("openajaxdialog", $AjaxEl);		
		$.each(oServerData, function(i, oAjaxOptions)
		{
			_ExecuteOpenAjaxDialog(oAjaxOptions);
		});
		
		// open confirmation dialogs
		$.each($.ServerData("openconfirmdialog", $AjaxEl), function(i, oServerData)
		{
			$.OpenConfirmDialog(oServerData.sName, oServerData.sText, oServerData.oButtons, oServerData.sAjaxContainerID);
		});
		
		_AlertUser($AjaxEl);
		if($AjaxEl==null) { _HandleClientRequests(); }
		
		// if the date needs the timezone offset, then send the ajax request
		var bGetTimezoneOffset = $.ServerData("js.date.getclienttimezoneoffset")[0];
		//console.log("getclienttimezoneoffset? %b", bGetTimezoneOffset);
		if(bGetTimezoneOffset==true)
		{
			$.post($.MapClientPath("/$Js/Core/Date/inc/AjxGetClientTimezoneOffset.asp"), {nTimezoneOffset:(new Date()).getTimezoneOffset()});
		}
	};	
	
	var _ApplyCollapsibleHeaders = function($AjaxEl)
	{
		$(".js-page-header", $AjaxEl).each(function()		
		{
			var $header = $(this);
			var oServerData = $header.ServerData();
			if(!oServerData) { return; }
			$header.html("<span class=\"ui-icon\"></span>"+$header.html());	
			var oEventData = $.extend({sExpandedUiIcon:"ui-icon-triangle-1-s",sCollapsedUiIcon:"ui-icon-triangle-1-e"}, oServerData);
			var bCollapsed = $.User.nContextData("Js.Form.Collapsible."+oEventData.sCollapsibleID);
			if(isNaN(bCollapsed)==true) { bCollapsed = oEventData.bDefaultCollapsed; }
			bCollapsed = Boolean(bCollapsed);
			var $container = $("#"+oEventData.sCollapsibleID);
			var nContainerBottomMargin = $container.css("margin-bottom");
			var nHeaderBottomMargin = $header.css("margin-bottom");
			CollapseExpand(true);
			$header.click(function() { bCollapsed = !bCollapsed; CollapseExpand(); })
				.mouseover(function() { $header.addClass("ui-state-hover"); })
				.mouseout(function(){ $header.removeClass("ui-state-hover"); });
			
			function CollapseExpand(bInit)
			{
				if(bCollapsed)
				{
					if(bInit) { $container.hide(); };
					$container.slideUp("fast", function()
					{
						$header.css("margin-bottom", nContainerBottomMargin)
							.removeClass("ui-state-active ui-corner-top")
							.addClass("ui-state-default ui-corner-all")
							.find(".ui-icon").removeClass(oEventData.sExpandedUiIcon)
								.addClass(oEventData.sCollapsedUiIcon);
					});	
					$.User.nContextData("Js.Form.Collapsible."+oEventData.sCollapsibleID, 1);				
				}
				else
				{
					$header.css("margin-bottom", nHeaderBottomMargin)
						.removeClass("ui-state-default ui-corner-all")						
						.addClass("ui-state-active ui-corner-top")
						.css("border-bottom", "0 !Important")
						.find(".ui-icon").removeClass(oEventData.sCollapsedUiIcon)
							.addClass(oEventData.sExpandedUiIcon);
					$container.slideDown("fast").css("border-top", 0);
					$.User.nContextData("Js.Form.Collapsible."+oEventData.sCollapsibleID, 0);
				}
			}
		});
	};
	
	var _ExecuteOpenAjaxDialog = function(oAjaxOptions)
	{	/* oAjaxOptions : {sUrl:, sAjaxContainerID:, nRefreshInterval:, oInParams:} */
		// see if we are opening a modal dialog
		if(oAjaxOptions.sAjaxContainerID==null)
		{
			var sID = String(oAjaxOptions.sUrl).split("/");
			sID = sID[sID.length-1].split(".")[0];
			oAjaxOptions.sAjaxContainerID = sID;
			oAjaxOptions.bUseInlineDialog = true;
		}
		if(oAjaxOptions.bUseInlineDialog == true) { _EnsureModalDialog(oAjaxOptions.sAjaxContainerID); }
		
		// reset the last focus since we are opening a new form
		jQuery.User.sContextData("LastFormTarget", null); 
		
		// add to the ajax history
		var $AjaxContainer = $("#"+oAjaxOptions.sAjaxContainerID);
		var oHist = $AjaxContainer.data("oAjaxHistory") || [];
		var oCurrentOptions = $AjaxContainer.data("oAjaxData");		
		if(oCurrentOptions != null) { oHist.push(oCurrentOptions); $AjaxContainer.data("oAjaxHistory", oHist); }
		
		$AjaxContainer.data("oAjaxData", {oAjaxOptions:oAjaxOptions, sPreviousHtml:$("#"+oAjaxOptions.sAjaxContainerID).html()});
		var oPostData = {
			__AJAXCONTAINERID: oAjaxOptions.sAjaxContainerID,
			__EVENTTARGET: "Form",
			__EVENTTYPE: "Opened",
			__EVENTARGUMENT: JSON.ToString({oInParams:oAjaxOptions.oInParams||{}})};
		_AjaxPost(oAjaxOptions.sUrl, oPostData, oAjaxOptions.sAjaxContainerID);
	};
	
	var _AjaxPost = function(sUrl, oPostData, sAjaxContainerID)
	{
		var $AjaxEl = $("#"+sAjaxContainerID);
		var oAjaxData = $AjaxEl.data("oAjaxData") || {};
		var oAjaxOptions =  oAjaxData.oAjaxOptions;
		//if(oAjaxOptions.bSlideIn || oAjaxOptions.bFadeIn) {oElement.hide(); }
		_bOkToBindScript = true;
		
		$.post(sUrl, oPostData, function(sResponse)
		{
			// see if this is a "Posted" event and if there is an "afterpost" event handler
			if((oPostData.__EVENTTYPE=="Posted" || String(oPostData).indexOf("__EVENTTYPE=Posted")>-1)
				&& $AjaxEl.triggerHandler("afterpost", {sResponse:sResponse, Response: function(){ return JSON.FromString(sResponse); }})===true)
			{	// handler returned true, so exit.
				 return;
			}
			
			if(oAjaxOptions && oAjaxOptions.bUseInlineDialog) { _OpenModalDialog(oAjaxOptions, sResponse); }
			else { $AjaxEl.html(sResponse); }
			$AjaxEl.AjaxContainer({bLoad:true});		
		});	
	};
	
	var _InitAjaxForm = function($AjaxEl)
	{	// used to set up a form after an ajax post	
		_CloseAjaxForm($AjaxEl); // test for close before preparing the form
		_PrepareForm($AjaxEl);
		
		// bind javascript callbacks now that the html has been loaded
		$.each(_oBoundScriptHandlers, function(sName, oFn) { oFn($AjaxEl); });
		_oBoundScriptHandlers = {};
		
		// handle client requests after bound script handlers (so controls can bind before client scripts)
		// was before preparing the form (is there are case for this)
		_HandleClientRequests($AjaxEl);
	};
	
	var _HandleClientRequests = function($Context)
	{
		$("clientrequest", $Context).each(function()
		{
			$this = $(this);
			var oHandler = _oClientRequestHandlers[$this.attr("type")];
			if(!oHandler) { return; }
			oHandler($("#"+$this.attr("target")), $this.ServerData());
		});	
	};
	
	var _EnsureModalDialog = function(sID)
	{
		var $Dialog = $("#"+sID);
		if($Dialog.length==0)
		{
			$("body").after("<div id=\""+sID+"\"></div>");
			$Dialog = $("#"+sID);
		}
		return $Dialog;
	};
	
	var _OpenModalDialog = function(oAjaxOptions, sHtml)
	{	
		var sizeWindow = function()
		{	// if there is an item to stretch its height, keep that at the bottom right of the dialog
			var oStretch = $Dialog.data("stretchHeight");
			var $stretch;
			if($.isFunction(oStretch)==true) { $stretch = oStretch(); }
			else { $stretch = $(oStretch); }
			if(!$stretch || $stretch.length === 0) { return; }			
			
			var nOriginalDif = $stretch.data("originalDif");
			if(!nOriginalDif)
			{
				nOriginalDif = $("form", $Dialog).outerHeight(true)+parseInt($Dialog.css("padding-top"))+parseInt($Dialog.css("padding-bottom"))-$stretch.height();				
				$stretch.data("originalDif", nOriginalDif);
			}				
			var nHeight = $Dialog.outerHeight(true)-nOriginalDif;			
			$stretch.height(Math.max(nHeight, 50));
		};
		
		var $Dialog = _EnsureModalDialog(oAjaxOptions.sAjaxContainerID);
		if(String(oAjaxOptions.sWindowSize).toLowerCase() == "small") { oAjaxOptions.nWindowWidth = 450; }
		else if(String(oAjaxOptions.sWindowSize).toLowerCase() == "large") { oAjaxOptions.nWindowWidth = 800; }
		else if(String(oAjaxOptions.sWindowSize).toLowerCase() == "larger") { oAjaxOptions.nWindowWidth = 900; }		
		if(!oAjaxOptions.nWindowWidth) { oAjaxOptions.nWindowWidth = 600; } // "medium"
		
		var bIsClosing = (sHtml.indexOf("<closedialog/>")>-1);
		$Dialog.html(sHtml);
		if(oAjaxOptions.sWindowTitle==null){ oAjaxOptions.sWindowTitle = $("#"+oAjaxOptions.sAjaxContainerID+" .js-page-title").html(); };
		
		$Dialog.dialog
		({
			modal: true,
			autoOpen: false,
			title: oAjaxOptions.sWindowTitle,
			width: oAjaxOptions.nWindowWidth,
			height: oAjaxOptions.nWindowHeight,
			show: oAjaxOptions.sWindowEffect,
			hide: oAjaxOptions.sWindowEffect,
			close: function()
			{	// clear the history when closing
				var $Dlg = $("#"+oAjaxOptions.sAjaxContainerID);
				$Dlg.data("oAjaxHistory", null);
				$Dlg.data("oAjaxData", null);				
				$Dlg.dialog("destroy").remove();
			},
			resize: function() { sizeWindow(); }			
		});
		
		// jch* doc this or come up with a better way to extend plugins
		// Can be a selector or a function - only one. Also could have stretchHeightMin.
		$Dialog.data("stretchHeight", function() 
		{	// if there is a .js-page-table-right us this to stretch
			var $table = $(".js-page-table-right", $Dialog).closest("table");
			if($table.length!=1) { return null; }
			return $table;
		});
		
		if(oAjaxOptions.bIsPageDialog!==false) { $Dialog.closest(".ui-dialog").addClass("js-page-dialog"); }
		$Dialog.dialog("open");
		sizeWindow();
		setTimeout(sizeWindow, 1);
		if(bIsClosing) { $Dialog.closest(".ui-dialog").remove(); }
	};
	
	var _CloseAjaxForm = function($AjaxEl)
	{
		var oOutParams = $.ServerData("js.form.ajaxoutparams", $AjaxEl)[0];
		if(!oOutParams) { /* the ajax form is not closing */ return; }
	
		var onAfterAlert = function()
		{
			if(oOutParams.oCloseParams.bRefresh) { return window.location.reload(true); }
			if(oOutParams.oCloseParams.sRedirectUrl) { return document.location = oOutParams.oCloseParams.sRedirectUrl }
			if(!sAjaxContainerID) { return; }
			
			// update the ajax history
			var oAjaxHistory = $AjaxEl.data("oAjaxHistory") || [];	
			var oAjaxData = $AjaxEl.data("oAjaxData");
			var sChildUrl = oAjaxData.oAjaxOptions.sUrl;
			var bHasParentHistory = (oAjaxHistory.length > 0);
			$AjaxEl.data("oAjaxData", oAjaxHistory.pop());
			$AjaxEl.data("oAjaxHistory", oAjaxHistory);

			var oParentAjaxData;
			if(bHasParentHistory==false)
			{
				if(oAjaxData.oAjaxOptions.bUseInlineDialog) { $("#"+sAjaxContainerID).dialog("destroy").remove(); }
				else
				{
					$AjaxEl.html(oAjaxData.sPreviousHtml);
					_PrepareForm($AjaxEl);
				}
				oParentAjaxData = $("#"+oAjaxData.oAjaxOptions.sParentAjaxContainerID).data("oAjaxData");
			}
			else
			{
				oParentAjaxData = $AjaxEl.data("oAjaxData");
			}
			
			var oArgument = {bSuccessful:oOutParams.bSuccessful, oOutParams:oOutParams.oOutParams};
			if(!oParentAjaxData) // no inline parent, do a full post
			{				
				if(oAjaxData.oAjaxOptions.bRootless != true)
				{
					$.SubmitEvent(sChildUrl, "Closed", oArgument);
				}
				return;
			}
			
			// use the previous history ajax options to go back to
			oAjaxData = oParentAjaxData;
			sAjaxContainerID = oParentAjaxData.oAjaxOptions.sAjaxContainerID;			
			var sPostData = [oAjaxData["sFormData"],"&1=1",
				"&__EVENTTARGET=",encodeURIComponent(sChildUrl),
				"&__EVENTTYPE=",encodeURIComponent("Closed"),
				"&__EVENTARGUMENT=",encodeURIComponent(JSON.ToString(oArgument)),
				"&__AJAXCONTAINERID=",sAjaxContainerID].join("");
			_AjaxPost(oAjaxData.oAjaxOptions.sUrl, sPostData, sAjaxContainerID);
		};
		
		// alert the user if needed
		var sAjaxContainerID = oOutParams.__AJAXCONTAINERID;
		var bAlertUser = _AlertUser($AjaxEl, onAfterAlert);
		if(bAlertUser == false) { onAfterAlert(); }
	};
	
	var _AlertUser = function($AjaxEl, oCallback)
	{	// returns true if there was an alert, false if none
		var oAlerts;
		if(arguments[0]===true) { sMessage = arguments[1]; }
		else
		{	
			oAlerts = $.ServerData("js.form.alerts", $AjaxEl)[0];
			if(!oAlerts) { return false; }
		}		
		$.AlertUser(oAlerts, {$Context:$AjaxEl, oOnCloseFn:oCallback});
		return true;
	};
	
	var _GetLabel = function(sControlName, $Context)
	{
		var sLabel = $("label[for='"+sControlName+"']", $Context).text() || "A Field";
		return String(sLabel).replace(/:/g, "");
	};
	
	var _OpenContextHelp = function(event, sHelpUrl)
	{			
		sHelpUrl = sHelpUrl || $(event.target).closest("form[helpurl]").attr("helpurl") || $("#Js_Form").attr("helpurl");
		if(!sHelpUrl) { return; }
		$.Window("help", sHelpUrl);
		return false;
	};
})(jQuery);