/**
 * Ax_AsyncCheck - Asynchronous Form Field Checker
 *
 * ---------------------------------------------------------------------------
 *
 * Perform periodic server-side background checks on form fields
 * and update result object (usually an IMG tag or IFRAME) accordingly.
 *
 * Can be used in parallel on multiple fields and result objects.
 * Will not make server calls unless (any of the) values have changed.
 * Max update frequency is configurable.
 *
 * The "result object" must have a generating script on the server which
 * pays attention to the query param passed to it, which will
 * contain the values of the fields being checked.  Whatever this script
 * returns is placed into the result object on the page via its "src"
 * attribute.  Any tag with a "src" attribute can be used, including
 * IMG tags (with server-side script generating an image) and 
 * IFRAME tags (with server-side script generating an HTML page).
 * Sending back 302 redirects is not advised as this introduces additional
 * non-interactive latency.
 *
 * Because the checker take an array of input IDs, it is possible to
 * tie a single result object to multiple inputs.  If any of the input
 * values change, the result object is updated with ALL of the fields
 * and their current values.
 *
 * ---------------------------------------------------------------------------
 * Usage:
 *  In body onload tag, call the ax_AsyncCheck_check() function once for each
 *  field set to check, passing params defining the operation.
 *  The rest is handled automatically.
 * 
 * ---------------------------------------------------------------------------
 * Example 1 (Basic):
 *
 *  To verify the input field with id "inpField" and put the result into the
 *  IFRAME with id "ifrmResult" using the checking script check.php,
 *  with a maximum update frequency of 1 second:
 *
 *    1a. Place in HEAD section:
 * 		<script type="text/javascript" src="/libAxonChisel/Ax_AsyncCheck.js"></script>
 * 		<script type="text/javascript">
 * 			function asynchCheck_MyField() { ax_AsyncCheck_checkSimple("inpField", "ifrmResult", "check.php"); }
 * 		</script>
 *
 *    1b. Place in body tag:
 *
 * 		<body onload="ax_AsyncCheck_enqueue('asynchCheck_MyField()', 1000);">
 *
 *  On the server, the script "check.php" will get called (GET) with a 
 *  "inpField" param (?inpField=xxx) when the field value needs to be
 *  verified.
 *
 * ---------------------------------------------------------------------------
 * Example 2 (Advanced):
 *
 *  To spell-check all text not within curly brackets ("{text}") in two
 *  textareas "txtInfo1" and "txtInfo2" and show results in an IMG with id 
 *  "imgSpellCheck" dynamically generated by script spellcheckImage.php, 
 *  with maximum update frequency of half a second:
 *
 *    2a. Place in HEAD section:
 * 		<script type="text/javascript" src="/libAxonChisel/Ax_AsyncCheck.js"></script>
 * 		<script type="text/javascript">
 * 			function asynchCheck_SpellCheck()
 * 			{
 * 				ax_AsyncCheck_check(
 * 					{
 * 						inputs:
 * 						[
 * 							{
 * 								inputId:		"txtInfo1",
 * 								outputParam:	"info1",
 * 								regexes:
 * 								[
 * 									{ find: "{[^}]*}", mods: "mg", repl: "" }
 * 								]
 * 							},
 * 							{
 * 								inputId:		"txtInfo2",
 * 								outputParam:	"info2",
 * 								regexes:
 * 								[
 * 									{ find: "{[^}]*}", mods: "mg", repl: "" }
 * 								]
 * 							}
 * 						],
 * 						resultId:	"imgSpellCheck",
 * 						resultUri:	"spellcheckImage.php"
 * 					}
 * 				);
 * 			}
 * 		</script>
 *
 *    1b. Place in body tag:
 *
 * 		<body onload="ax_AsyncCheck_enqueue("asynchCheck_SpellCheck()", 500);">
 *
 *  On the server, the script "spellcheckImage.php" will get called (GET) with
 *  params info1 and info2 (?info1=xxx&info2=zzz) when the text needs to be 
 *  spell-checked, and will construct and output an actual raw GIF image 
 *  (eg a green checkmark or a red X).
 *
 *
 * ---------------------------------------------------------------------------
 * Version: 2007-06-02
 * ---------------------------------------------------------------------------
 *
 * LICENSE:
 *
 * For use only with explicit permission from Dan Kamins / AxonChisel.net.
 *
 * ---------------------------------------------------------------------------
 * @author Dan Kamins [d a x  A.T  a x o n c h i s e l  D.O.T  n e t]
 * @package libAxonChisel
 * @tab 4
 * ---------------------------------------------------------------------------
 * Copyright (c) 2006, AxonChisel.net
 * ---------------------------------------------------------------------------
 */


/** 
 * Global map of input field id (string) to last seen value (string). 
 * Input fields are actually prefixed by result obj id + "/" to allow
 * multiple result objects to observe the same input field.
 */
var ax_AsyncCheck_gmssLastFieldVal = {};





/**
 * Perform a field check, updating result object src if any changes.
 *
 * This function is typically called on an interval as set up by
 * ax_AsynchCheck_enqueue().
 *
 * @param map(string, any) msaCommand	Command definition for check.
 *
 *			Contains properties:
 *				inputs : array of map objects, one per input field to check.
 *				resultId : element ID of result object to update on changes.
 *				resultUri : URI base for result object to update on changes.
 *				execOnChange : (optional) JavaScript to execute on change.
 *
 *			The "inputs" array contains an object for each field to check,
 *			containing the following properties:
 *				inputId : element ID of input field to check value of.
 *				outputParam : (optional) query param name for changed value.
 *								If not specified, uses inputId.
 *				regexes : array of regex replacement definitions.
 *
 *			Each regex definition is a map with the following properties:
 *				find : regex pattern (no delimiters) to search field value for.
 *				mods : pattern modifiers (string including flags from "gim").
 *				repl : replacement for found patterns. $backrefs allowed.
 *
 *			(Regexes are optional but commonly used for compressing data or 
 *			manipulating it into a form expected by the result object script.)
 *			
 *
 * @author Dan Kamins [d a x  A.T  a x o n c h i s e l  D.O.T  n e t]
 */
function ax_AsyncCheck_check( msaCommand )
{
	
	// Parse input command object:
	var armsaInputs			= msaCommand["inputs"];
	var sIdResultObj		= msaCommand["resultId"];
	var sUriResultObj		= msaCommand["resultUri"];
	var sExecOnChange		= msaCommand["execOnChange"];

	// Iterate input fields, looking for changes:
	var bChanges = false;
	for (var i = 0; i < armsaInputs.length; ++i)
	{
		// Parse input element:
		var sInputId		= armsaInputs[i]["inputId"];

		// Check new and old values:
		var eEl = document.getElementById(sInputId);
		if (eEl == undefined) { continue; }
		var sValue = eEl.value;
		var sLastValue = ax_AsyncCheck_gmssLastFieldVal[sIdResultObj + "/" + sInputId];
		
		// If changed, store new value and raise changed flag:
		if (sValue != sLastValue) 
		{
			bChanges = true; 
			ax_AsyncCheck_gmssLastFieldVal[sIdResultObj + "/" + sInputId] = sValue;
		}
	}

	// If no values changed since last call, return:
	if (!bChanges)
	{
		return;
	}

	// Generate unique instance number for URL to prevent caching:
	var sInstanceNum = "" + new Date().getTime(); // (time now in ms)

	// Build new target for result obj src:
	var sUriNew = sUriResultObj;
	if (sUriNew.indexOf("?") == -1) { sUriNew = sUriNew + "?"; }
	sUriNew = sUriNew + "&_inst="+sInstanceNum;

	// Append input field values:
	for (var i = 0; i < armsaInputs.length; ++i)
	{
		// Parse input element:
		var sInputId		= armsaInputs[i]["inputId"];
		var sOutputParam	= armsaInputs[i]["outputParam"];
		var armsaRegexes	= armsaInputs[i]["regexes"];
		if (sOutputParam == undefined) { sOutputParam = sInputId; }

		// Get current value:
		var eEl = document.getElementById(sInputId);
		if (eEl == undefined) { continue; }
		var sValue = eEl.value;

		// Execute regexes associated with input field:
		if (armsaRegexes != undefined)
		{
			for (var iRegex = 0; iRegex < armsaRegexes.length; ++iRegex)
			{
				var reRegex = new RegExp(armsaRegexes[iRegex]["find"], armsaRegexes[iRegex]["mods"]);
				sValue = sValue.replace(reRegex, armsaRegexes[iRegex]["repl"]);
			}
		}

		// Perform URL encoding and append query param:
		var sValueEsc = encodeURIComponent(sValue);						
		sUriNew += "&" + sOutputParam + "=" + sValueEsc;
	}

	// Point result object to new src:
	document.getElementById(sIdResultObj).src = sUriNew;
	
	// Execute scriplet on change event:
	if (sExecOnChange != "")
	{
		eval(sExecOnChange);
	}

} /* /ax_AsyncCheck_check() */



/**
 * Macro for calling ax_AsynchCheck_check() in common simple use case.
 * Assumes single input field associated with single simple result object.
 */
function ax_AsyncCheck_checkSimple( sInputId, sResultId, sResultUri )
{
	
	ax_AsynchCheck_check(
		{
			inputs:
			[
				{
					inputId:		sInputId,
					outputParam:	sInputId,
					regexes:		[]
				}
			],
			resultId:	sResultId,
			resultUri:	sResultUri
		}
	);

} /* /ax_AsyncCheck_checkSimple() */



/**
 * Setup macro for use at page load time - calls and enqueues period checks.
 *
 * @param string sFunctionName	Name of function (with trailing "()") to call 
 *								at regular intervals.
 *								Function should call ax_AsynchCheck_check()
 *								with appropriate arguments.
 * @param int iPeriodMillis		Millisecond interval between calls to checking
 *								function.
 */
function ax_AsyncCheck_enqueue(sFunctionName, iPeriodMillis)
{

	eval(sFunctionName);
	window.setInterval(sFunctionName, iPeriodMillis);

} /* /ax_AsyncCheck_enqueue() */



