/* Client side JS delegate for the panel framework (see baseFields.cfc)
 * (#this.jss# is an instance of PanelController)
 */

/* Validated with http://jslint.com/ Edition 2010-04-06 */

/*jslint onevar: true, browser: true, evil: true, forin: true */
/*global Ext:false,DWREngine:false, trim:false,
 base64Decode:false, applyEmbeddedCSS:false, applyEmbeddedJS:false,
 util:false, window:false*/

function PanelController(){}

/**
 * Global event manager that any can register to receive events on
 * and anyone can post events on.
 * Used by panels to communicate with each other.
 * But can also be used by any other JavaScript.
 */
var EventManager =
{
	preEventListeners: {},
	postEventListeners: {},

	addPreListener: function(name, f)
	{
		if (name in this.preEventListeners)
		{
			this.preEventListeners[name].push(f);
		}
		else
		{
			this.preEventListeners[name] = [f];
		}
	},

	addPostListener: function(name, f)
	{
		if (name in this.postEventListeners)
		{
			this.postEventListeners[name].push(f);
		}
		else
		{
			this.postEventListeners[name] = [f];
		}
	},

	fireBeforeEvent: function (name, payload, source)
	{
		var listeners,i,j;
		if (name in this.preEventListeners)
		{
			listeners = this.preEventListeners[name];
			for (i=0,j=listeners.length;i<j;i++)
			{
				listeners[i](payload, source);
			}
		}
	},

	fireAfterEvent: function(name, payload, source, resp)
	{
		var listeners,i,j;
		if (name in this.postEventListeners)
		{
			listeners = this.postEventListeners[name];
			for (i=0,j=listeners.length;i<j;i++)
			{
				listeners[i](payload, source, resp);
			}
		}
	}
};


(function() {

	// private variables, data structures, & functions

	var getFormData = function()
	{
		var loadFormElementData = function(formData, ele, lookForSurrogates)
		{
			var j,surrogateObj;

			if (!formData[ele.name])
			{
				formData[ele.name] = "";
			}

			if (ele.type == "select-multiple" && ele.options && ele.options.length > 0)
			{
				for (j = 0; j < ele.options.length; j++)
				{
					if (ele.options[j].selected)
					{
						if (formData[ele.name].length > 0 && ele.options[j].value.length > 0)
						{
							formData[ele.name] = formData[ele.name] + ",";
						}
						formData[ele.name] = formData[ele.name] + ele.options[j].value;
					}
				}
			}
			else if (ele.type == "checkbox" || ele.type == "radio")
			{
				if (ele.checked)
				{
					if (formData[ele.name].length > 0 && ele.value.length > 0)
					{
						formData[ele.name] = formData[ele.name] + ",";
					}
					formData[ele.name] = formData[ele.name] + ele.value;
				}
				if (lookForSurrogates && ele.type == "checkbox")
				{
					// look for surrogate VAL (value) object used by checkboxes and get its value
					surrogateObj=document.getElementById(ele.id+"_VAL");
					if (surrogateObj)
					{
						if (!formData[surrogateObj.name])
						{
							formData[surrogateObj.name] = "";
						}
						if (formData[surrogateObj.name].length > 0 && surrogateObj.value.length > 0)
						{
							formData[surrogateObj.name] = formData[surrogateObj.name] + ",";
						}
						if (ele.checked)
						{
							formData[surrogateObj.name] = formData[surrogateObj.name] + '1';
						}
						else
						{
							formData[surrogateObj.name] = formData[surrogateObj.name] + '0';
						}
					}
				}
			}
			else
			{
				if (formData[ele.name].length > 0 && ele.value.length > 0)
				{
					formData[ele.name] = formData[ele.name] + ",";
				}
				formData[ele.name] = formData[ele.name] + ele.value;
			}
		}, form, formData = {}, i;

		if (document.forms[this.form])
		{
			form = document.forms[this.form];
			for (i = 0; i < form.elements.length; i++)
			{
				loadFormElementData(formData, form.elements[i], false);
			}
		}
		else
		{
			for (i in this.fields)
			{
				loadFormElementData(formData, this.fields[i], true);
			}
		}
		return formData;
	};

	// apply methods and properties to PanelController "class"
	Ext.apply(PanelController.prototype, {
	  cssLoaded: false,
		submitted: false,
		submitbtn: null,
		ajaxUri: '/resources/ajax/ajaxPanels.cfc',
		lastSubmitFormData: {},
		/*
			config is called by the framework when the panel first
			comes up and on each round trip to the server.

			info is an array of field config objects pertaining to fields that
			were actually rendered when the display method was called.

			settings is a copy of the server side panel settings struct.
		*/
		config: function(info, settings)
		{
			var f,i,j,tryToFocus=false,that=this,requiredFieldCount=0,requiredDiv;

			this.fieldInfo=info;
			this.settings=settings;
			if (this.settings.noaudit === true)
			{
				this.settings.noauditparm = "?noaudit=true";
			}
			else
			{
				this.settings.noauditparm = "";
			}

			// create lookup of field dom elements, keyed by field name
			this.fields={};

			// create array of fields that have errors
			this.errorFieldInfo=[];

			for (i=0,j=info.length;i<j;i++)
			{
				f=info[i];
				this.fields[f.fieldname]=document.getElementById(f.domid);
				if (f.haserror)
				{
					tryToFocus=true;
					this.errorFieldInfo.push(f);
				}
				if (f.focus)
				{
					tryToFocus=true;
				}
				if (f.required)
				{
					requiredFieldCount++;
				}
				if (f.finderfieldassociations)
				{
					f.finderfieldassociations =
						Ext.util.JSON.decode(f.finderfieldassociations);
				}
				// bind any found finder panels back to fields in this panel
				if (f.finderpanel && window[f.finderpanel])
				{
					window[f.finderpanel].onRowSelected =
						this.onFind.createDelegate(this, [f], true);
				}
			}
			requiredDiv = document.getElementById(this.form+"Required");
			if (requiredDiv)
			{
				if (requiredFieldCount > 0)
				{
					requiredDiv.style.display = "block";
				}
				else
				{
					requiredDiv.style.display = "none";
				}
			}

			if (tryToFocus)
			{
				/* async focus on first field with error, or first user focus field */
				setTimeout(function(){that.doFieldFocus();},0);
			}
		},

		onBefore: function(action, f, dest)
		{
			if (!dest)
			{
				dest = this;
			}
			EventManager.addPreListener(action, function()
			{
				f.apply(dest,arguments);
			});
		},

		onAfter: function(action, f, dest)
		{
			if (!dest)
			{
				dest = this;
			}
			EventManager.addPostListener(action, function()
			{
				f.apply(dest,arguments);
			});
		},

		/* functions for manually firing generic events */
		fireBeforeEvent: function(action, payload)
		{
			EventManager.fireBeforeEvent(action, payload, this);
		},
		fireAfterEvent: function(action, payload)
		{
			EventManager.fireAfterEvent(action, payload, this);
		},

		getField: function(fieldName)
		{
			return this.fields[fieldName];
		},

		/* attempt to set focus to the first field that has an error
					if that fails, try the next field, etc.
					if no fields have an error defined, set focus to first field
					that has focus=true that succeeds
		*/
		doFieldFocus: function()
		{
			var f,i,j;

			/*
				if one or more fields have an error, focus on the first
				field possible.
			*/
			if (this.errorFieldInfo.length)
			{
				for (i=0,j=this.errorFieldInfo.length;i<j;i++)
				{
					f=this.errorFieldInfo[i];
					try
					{
						this.fields[f.fieldname].focus();
						return;
					}
					catch (ex1)
					{
					}
				}
			}
			else
			{
				/*
					if not error, focus on first field
					that has focus set.  if that fails, try the next one, etc.
				*/
				for (i=0,j=this.fieldInfo.length;i<j;i++)
				{
					f=this.fieldInfo[i];
					if (f.focus)
					{
						try
						{
							this.fields[f.fieldname].focus();
							return;
						}
						catch (ex2)
						{
						}
					}
				}
			}
		},

		/*

			connect fields together via the enter key
			so that pressing enter on a field will advance the focus to the
			next field in the specified order similar to how tab normally does
			except that buttons that are found are connected to the onclick
			handler so that they can submit the panel by
			hitting enter from the previous field.

			fields is a list of field names and|or dom ids in the order that they
			should be threaded together.

		*/
		threadFields: function(fields)
		{
			var i,j,field,last;

			function threadToField(field)
			{
				if (field.getAttribute("type") === "button")
				{
					return function(e) {
						var c;
						e=e||event;
						c=e.keyCode||e.which;
						if (c===13)
						{
							/* route to button click */
							field.onclick(e);
							return false;
						}
					};
				}
				else
				{
					return function(e) {
						var c;
						e=e||event;
						c=e.keyCode||e.which;
						/* route to focus on next field */
						if (c===13)
						{
							field.focus();
							return false;
						}
					};
				}
			}

			if (fields && fields.length)
			{
				for (i=0,j=fields.length; i<j; i++)
				{
					field=this.getField(fields[i]);

					/* if field not found directly, look for it by id */
					if (!field)
					{
						field = document.getElementById(fields[i]);
					}

					if (field)
					{
						if (last)
						{
							last.onkeydown = threadToField(field);
						}
						last=field;
					}
				}
			}
		},

		load: function(parms)
		{
			var formData, n, source=this;

			if (this.settings.waitmsgactive)
			{
				this.showWaitMsg(this.loadingMessage);
			}

			formData = Ext.apply({},{cfc:this.cfc},this.request.parm);

			if (parms)
			{
				for (n in parms)
				{
					formData[n.toLowerCase()] = parms[n];
				}
			}
			if (!formData.action)
			{
				formData.action = "";
			}
			EventManager.fireBeforeEvent('load', formData, source);
			DWREngine._execute(this.ajaxUri + this.settings.noauditparm,
				null, "loadCfc", formData, function(resp)
				{
					resp.hasError = (resp.haserror === 'true');
					delete resp.haserror;
					source.loadResponse(resp);
					EventManager.fireAfterEvent('load', formData, source, resp);
				});
		},

		fnSubmit: function(action, button, waitMsg)
		{
			var parms;

			if (typeof(action) === "object")
			{
				parms = action;
			}
			else
			{
				parms = {action:action, button:button, waitMsg:waitMsg};
			}

			if (!this.submitted)
			{
				this.doSubmit(parms, false);
			}
		},
		fnDownload: function(parms)
		{
			if (!this.submitted)
			{
				if (!parms)
				{
					parms = {action:""};
				}
				parms._dl_ = "true";
				this.doSubmit(parms, true);
			}
		},
		fnDownloadDirect: function(parms)
		{
			var urlParms = "",form=document.forms[this.form],n;
			if (parms)
			{
				for (n in parms)
				{
					urlParms = urlParms + "&" + n + "=" + parms[n];
				}
			}
			this.clearFieldsNotDisplayed();
			form.action =
				this.ajaxUri + "?method=downloadCfc&cfc=" + this.cfc + urlParms;
			form.method = "post";
			if (parms.formtarget)
			{
				form.target = parms.formtarget;
			}
			form.submit();
			form.action = "";
			form.method = "";
			form.target = "";

			if (!parms.keepsubmitlock)
			{
				this.clearSubmitLock();
			}
		},
		fnUpload: function(parms)
		{
			if (!this.submitted)
			{
				if (!parms)
				{
					parms = {action:""};
				}
				parms._ul_ = "true";
				this.doSubmit(parms, true);
			}
		},
		fnUploadDirect: function(parms)
		{
			var urlParms = "",that=this,form=document.forms[this.form],
				ifname,iframe,n;
			if (parms)
			{
				for (n in parms)
				{
					urlParms = urlParms + "&" + n + "=" + parms[n];
				}
			}
			ifname = ("uploader" + (new Date()).getTime());
			iframe = Ext.DomHelper.insertFirst(Ext.getBody(), {
				tag:"iframe", id:ifname, name:ifname, src:"about:blank",
				frameBorder:1, scrolling:"yes", height:"200", width:"600",
				style:"display:none;"});

			Ext.get(iframe).on("load", function() {
				var content;
				that.clearSubmitLock();
				content = trim(frames[ifname].document.body.innerHTML);
				setTimeout(function(){Ext.get(iframe).remove();}, 100);
				eval(base64Decode(content));
			});

			this.clearFieldsNotDisplayed();
			form.action = this.ajaxUri + "?method=uploadCfc&cfc=" + this.cfc +
				urlParms;
			form.method = "post";
			form.target = ifname;
			form.enctype = "multipart/form-data";
			form.encoding = "multipart/form-data";
			form.submit();
			form.action = "";
			form.method = "";
			form.target = "";
			form.enctype = "text/plain";
			form.encoding = "text/plain";
		},
		loadResponse: function(resp)
		{
			if (resp.script && trim(resp.script).length > 0)
			{
				eval(resp.script);
			}
			else if (trim(resp.html).length > 0)
			{
				document.getElementById(this.form + "Div").innerHTML = resp.html;
				if (!this.cssLoaded)
				{
					applyEmbeddedCSS(resp.html);
					this.cssLoaded  = true;
				}

				if ('scriptconfigjss' in resp && trim(resp.scriptconfigjss).length > 0)
				{
					eval(resp.scriptconfigjss);
				}

				applyEmbeddedJS(resp.html);
				this.clearSubmitLock();
			}
			this.reposition();
		},
		doSubmit: function(parms, carryOverParms)
		{
			var waitMsg, formData = {}, n, button, source = this,
				fn = this.loadResponse;

			if (!parms.action)
			{
				parms.action = "";
			}

			if (this.settings.waitmsgactive)
			{
				waitMsg = "Please Wait. . .";
				if (parms.waitMsg)
				{
					waitMsg = parms.waitMsg;
					delete parms.waitMsg;
				}
				this.showWaitMsg(waitMsg);
			}

			this.submitted = true;

			if (parms.button)
			{
				button = parms.button;
				delete parms.button;
				this.submitbtn = button;
				this.submitbtn.disabled = true;
			}

			if (parms.cb && typeof(parms.cb) === 'function')
			{
				fn = parms.cb;
			}

			this.clearFieldsNotDisplayed();
			formData = getFormData.apply(this);
			formData.cfc=this.cfc;

			for (n in parms)
			{
				if (typeof(parms[n]) !== 'function')
				{
					formData[n] = parms[n];
					// identify parms to be carried over on uploads and downloads
					if (carryOverParms)
					{
						formData["p$" + n] = parms[n];
					}
				}
			}
			this.lastSubmitFormData = formData;

			EventManager.fireBeforeEvent(parms.action, formData, source);

			DWREngine._execute(this.ajaxUri + this.settings.noauditparm,
				null, "submitCfc", formData, function(resp)
				{
					resp.hasError = (resp.haserror === 'true');
					delete resp.haserror;

					fn.apply(source, arguments);
					EventManager.fireAfterEvent(parms.action, formData, source, resp);
				});

				//fn.createDelegate(this));
		},

		clearSubmitLock: function()
		{
			if (this.settings.waitmsgactive)
			{
				this.hideWaitMsg();
			}
			this.submitted = false;
			if (this.submitbtn)
			{
				this.submitbtn.disabled = false;
				this.submitbtn = null;
			}
		},
		showWaitMsg: function(msg)
		{
			this.waitMask = new Ext.LoadMask(this.form + "Cont",
				{msg:msg, removeMask:true});
			this.waitMask.show();
		},
		hideWaitMsg: function()
		{
			if (this.waitMask)
			{
				this.waitMask.hide();
				delete this.waitMask;
			}
		},

		/*
		  Create the functions for popup mode even if not using popup mode.
			If they are called they won't do anything unless this.settings.float
			is true
		*/
		show: function(parms)
		{
			var content, windowDefinition, that=this;

			if (this.settings.float)
			{
				if (!this.popupWin)
				{
					content = Ext.get(this.form + "Cont");
					content.show();
					windowDefinition = {
						width:       14 + content.getWidth(),
						closeAction: 'hide',
						items:       content,
						modal:       true,
						resizable:   false,
						bwrapCfg:    {cls: 'ass-ExtHideBwrap'},
						defaultButton: {focus:function(){that.doFieldFocus();}}
					};

					if (this.settings.title)
					{
						windowDefinition.title = this.settings.title;
					}

					this.popupWin = new Ext.Window(windowDefinition);
				}

				this.popupWin.purgeListeners();

				// apply parameters (we can add more parms here later)
				if (parms)
				{
					/* title for popup win */
					if ('title' in parms)
					{
						this.popupWin.setTitle(parms.title);
					}
				}
				this.popupWin.show(undefined,this.reposition,this);

				// convert ExtJS event to PanelController event
				this.popupWin.on('hide', function() {
					that.fireAfterEvent('hide');
				});

				// the following line should not be necessary
				// but (apparently) there is a bug in extjs
				// (window doesn't seem to focus on 3rd and subsequent hide/show)
				this.popupWin.focus();
			}
		},

		/*
			Alternatively, show a floating popup in a finder selected row.
		 */
		showInFinder: function(finder)
		{
			var content = Ext.get(this.form + "Cont");
			if (content && finder && finder.finderSelectedRowInfo.actionDiv)
			{
				content.appendTo(finder.finderSelectedRowInfo.actionDiv);
				content.show();
				this.shownInFinder = true;
			}
		},

		hideInFinder: function()
		{
			var panelHolderDiv = Ext.get(this.form + '_panel_holder'), 
				content = Ext.get(this.form + "Cont");

			this.shownInFinder = false;
			if (panelHolderDiv && content)
			{
				content.hide();
				panelHolderDiv.insertFirst(content);
			}
		},

		reposition: function()
		{
			var pos;
			if (this.popupWin && this.popupWin.isVisible())
			{
				this.popupWin.center();
				pos = this.popupWin.getPosition();
				if (pos[1] < 0)
				{
					this.popupWin.setPagePosition(pos[0], 0);
				}
			}
		},

		hide: function()
		{
			if (this.shownInFinder)
			{
				this.hideInFinder();
			}
			else
			{
				if (this.settings.waitmsgactive)
				{
					this.hideWaitMsg();
				}
				if (this.popupWin)
				{
					this.popupWin.hide();
				}
			}
		},

		onKeyPress: function(e)
		{
			if(e.which == 13 || e.keyCode == 13)
			{
				var button = document.getElementById(this.settings.clickbtnidonenter);
				if (button)
				{
					button.click();
				}
				e.keyCode = 0;
			}
		},

		setTitle: function(newTitle)
		{
			var span = document.getElementById('_' + this.form + 'TitleSpan_');
			if (span)
			{
				span.firstChild.nodeValue = newTitle;
			}
		},

		/**************************************************************************/
		/* additional methods and properties to support finder framework
			(see baseFinder.cfc) */


		// row that is selected when user clicks on it
		finderSelectedRow: undefined,
		finderSelectedRowInfo: undefined,
		// row that is built below selected row, just for doing actions
		finderActionRow: undefined,

		// "spreadsheet" of data that is currently in the finder grid.
		finderGridData: undefined,

		numfinderCols: 0,

		/**
		 * If panel extends baseFinder, this is called each time the grid is
		 * rendered or re-rendered.
		 *
		 * @param numFinderCols number of columns in the finder grid.
		 * @param data json for grid contents
		 */
		configGrid: function(numFinderCols, data)
		{
			var tbl = document.getElementById(this.form+"_GridTable"),that=this;

			// re-init selected row, if any
			this.finderSelectedRow = this.finderSelectedRowInfo =
				this.finderActionRow = undefined;

			this.numFinderCols = numFinderCols;
			this.finderGridData = data;
			tbl.onclick=function(e){that.finderTableClicked(e);};
		},

		doSort: function(hdr)
		{
			var existingSort = this.fields.$_tSortField.value,
				sort = hdr.getAttribute('sortname'),
				sortDir=this.fields.$_tSortDirection.value;

			if (existingSort===sort)
			{
				// if clicking on the field that is already sorted, flip the sort
				// direction.
				if (sortDir==='ASC')
				{
					sortDir='DESC';
				}
				else
				{
					sortDir='ASC';
				}
			}
			else  // if it's a new sort, always reset to ASC
			{
				sortDir='ASC';
			}

			this.fnSubmit({action:'query', $_tSortField:sort,
				$_tSortDirection:sortDir});
		},

		/**
		 * Build the div and related elements that pops up when a finder row is selected.
		 */
		buildActionDiv: function()
		{
			var actionRow = document.createElement('tr'),
				actionCol = document.createElement('td'),
				actionDiv = document.createElement('div');

			actionDiv.id=this.form+'-actiondiv';
			//actionDiv.className='ass-Reset';
			actionRow.className='ass-FinderAction';
			actionCol.colSpan=this.numFinderCols;
			actionCol.appendChild(actionDiv);
			actionRow.appendChild(actionCol);

			return {actionDiv:actionDiv, actionRow:actionRow, actionCol:actionCol};
		},

		/**
		 * Event handler called whenever the grid table is clicked
		 * handles sorting if a header is click, and row selecting if a row
		 * is clicked.
		 */
		finderTableClicked: function(e)
		{
			var tgt;

			// cross browser event normalization
			e=e||event;
			tgt=e.target||e.srcElement;

			if (tgt.getAttribute('sortname') !== null)
			{
				this.doSort(tgt);
				return;
			}

			// walk up the ancestor chain until we find a table row
			while (tgt && tgt.nodeName!=='TR')
			{
				tgt=tgt.parentNode;
			}

			// did we find a valid row?
			if (tgt && tgt!==this.finderActionRow && tgt!==this.finderSelectedRow &&
					tgt.getAttribute("rowidx") !== null)
			{
				this.createSelection(tgt);
			}
		},

		/**
		 * Select row n.
		 * Where n can be one of:
		 *
		 * 1. the actual tr dom element that was clicked on.
		 * 2. the 1-based index number of the row to select.
		 * 3. an object containing partial (or full) row data to search the grid
		 * for, for a row to select.
		 *
		 * If the row specifier is unspecified, or the specified row is not found,
		 * then do nothing, other than clear the existing selection, if any.
		 *
		 * return true if the selected row was found, otherwise return false.
		 */
		createSelection: function(n)
		{
			var tbod, i, j, x, tr, d, sib, matched;

			// cancel previous selection, if any
			this.cancelSelection();

			if (n===null||n===undefined)
			{
				return false;
			}

			if (typeof n==='object')
			{
				if (n.getAttribute)
				{
					i = n.getAttribute('rowidx');
					if (i !== null && i !== undefined)
					{
						i = parseInt(i,10);
						if (i > 0)
						{
							tr = n;
						}
					}
				}
				else
				{
					for (i=0,j=this.finderGridData.length; i<j; i++)
					{
						matched = true;
						for (x in n)
						{
							if (typeof n[x] !== 'function' &&
									n[x] !== this.finderGridData[i][x])
							{
								matched = false;
								break;
							}
						}
						if (matched)
						{
							// fall through to complete the rest of the selection
							n = i+1;
							break;
						}
					}
				}
			}

			// select based on row number
			if (typeof n==='number')
			{
				if (n > 0)
				{
					tbod = document.getElementById(this.form + "_GridTbody");
					if (tbod && tbod.children.length >= n)
					{
						tr = tbod.children[n-1];
						i = n;
					}
				}
			}

			// found actual row?
			if (tr)
			{
				d = this.finderGridData[i-1];

				// select row, visually
				this.finderSelectedRow = Ext.get(tr);
				this.finderSelectedRow.addClass('ass-FinderRowSelected');

				// save info related to selected row in a nice little package so
				// it can be used later.
				this.finderSelectedRowInfo =
					Ext.apply({rowIdx:i, rowTr:tr, rowData:d},this.buildActionDiv());

				this.finderActionRow = this.finderSelectedRowInfo.actionRow;

				sib=tr.nextSibling;
				if (sib)
				{
					tr.parentNode.insertBefore(this.finderSelectedRowInfo.actionRow,sib);
				}
				else
				{
					tr.parentNode.appendChild(this.finderSelectedRowInfo.actionRow);
				}

				if (this.onRowSelected(this.finderSelectedRowInfo))
				{
					this.fnSubmit(
						Ext.apply({action:'select',
							cb: function(resp)
							{
								if (resp.script && trim(resp.script).length > 0)
								{
									eval(resp.script);
								}
								else if (trim(resp.html).length > 0)
								{
									this.finderSelectedRowInfo.actionDiv.innerHTML = resp.html;
									if (!this.cssLoaded)
									{
										applyEmbeddedCSS(resp.html);
										this.cssLoaded  = true;
									}
									applyEmbeddedJS(resp.html);
									this.clearSubmitLock();
								}
							}

						}, this.finderSelectedRowInfo.rowData)
					);
				}
				return true;
			}
			return false;
		},

		cancelSelection: function()
		{
			// remove action row if any, and un-select last selected row, if any
			this.onRowDeselected(this.finderSelectedRowInfo);

			if (this.finderActionRow)
			{
				this.finderActionRow.parentNode.removeChild(this.finderActionRow);
				this.finderActionRow = undefined;
			}

			if (this.finderSelectedRow)
			{
				this.finderSelectedRow.removeClass('ass-FinderRowSelected');
				this.finderSelectedRowInfo = this.finderSelectedRow = undefined;
			}
		},

		getSelectedRowInfo: function()
		{
			var i={rowIdx:0, rowTr:undefined, rowData:{}};

			if (this.finderSelectedRow)
			{
				i.rowTr = this.finderSelectedRow;
				i.rowIdx = i.rowTr.getAttribute('rowidx');
				if (i.rowIdx !== null && i.rowIdx !== undefined)
				{
					i.rowIdx = parseInt(i.rowIdx,10);
				}
				else
				{
					i.rowIdx = 0;
				}
			}

			if (i.rowIdx > 0)
			{
				i.rowData = this.finderGridData[i.rowIdx-1];
			}
			return i;
		},

		/**
		 * Called on the source panel when a finder link is clicked on a field.
		 */
		triggerFinder: function(fieldName, finderPanelJss)
		{
			if (finderPanelJss && window[finderPanelJss])
			{
				window[finderPanelJss].clearFields();
				window[finderPanelJss].show();
				window[finderPanelJss].load();
			}
		},

		/**
		 * This may be overridden in order to provide custom row selection
		 * behavior.
		 *
		 * Should return false to prevent the default action (which would be
		 * sending a submit action to the server, building an additional "action"
		 * row below the selected row and displaying content there from the
		 * server submission.
		 *
		 * @param rowInfo - a json structure containing 3 elements:
		 * {
		 *   rowIdx: 1-based index of row selected
		 *   rowTr: the table row dom element that was selected.
		 *   rowData: json structure containing the field data for the row selected.
		 * }
		 */
		onRowSelected: function(rowInfo)
		{
			this.selectedRowInfo = rowInfo;
			return true;
		},

		/**
		 * This may be overridden.
		 * Should return false to prevent default action.
		 */
		onRowDeselected: function(rowInfo)
		{
			delete this.selectedRowInfo;
			return true;
		},

		/**
		 * Method called when a field finder row is selected in the target finder
		 * panel.  Default functionality transfers over associated fields.
		 * Can be overridden to provide custom functionality.
		 */
		onFind: function(rowInfo, fieldInfo)
		{
			var i,assoc=fieldInfo.finderfieldassociations;
			if (assoc)
			{
				for (i in assoc)
				{
					if (i in this.fields && assoc[i] && assoc[i] in rowInfo.rowData)
					{
						this.setFieldValue(i,rowInfo.rowData[assoc[i]]);
					}
				}
			}

			// hide finder panel
			window[fieldInfo.finderpanel].hide();
			return false;	// stop the default onRowSelected behavior
		},

		/**
		 * Set a field to a particular value.
		 *
		 * In most cases, user can just use
		 * #this.jss#.fields[fieldName].value = 'xxx' notation
		 * to directly set a value on a field.
		 *
		 * This method will also set textonly fields properly by setting
		 * the text inside the surrogate display object span, if it exists.
		 */
		setFieldValue: function(fieldName, value)
		{
			var field = this.fields[fieldName],surrogateObj,inputType;
			if (field)
			{
				if (field.tagName && field.tagName.toLowerCase() == "input")
				{
					inputType = field.type.toLowerCase();
					if (inputType == "checkbox" || inputType == "radio")
					{
						field.checked = (field.value === value);

						if (inputType == "checkbox")
						{
							// look for surrogate VAL (value) object used by checkboxes and set its value
							surrogateObj=document.getElementById(field.id+"_VAL");
							if (surrogateObj)
							{
								if (field.checked)
								{
									surrogateObj.value = '1';
								}
								else
								{
									surrogateObj.value = '0';
								}
							}
						}
					}
					else
					{
						field.value = value;
					}
				}
				else
				{
					field.value = value;
				}

				// also look for surrogate DO (displayonly, aka textonly) object and set its value
				surrogateObj=document.getElementById(field.id+"_DO");
				if (surrogateObj)
				{
					util.removeAllChildren(surrogateObj);
					surrogateObj.appendChild(document.createTextNode(value));
				}
			}
		},

		/**
		 * Client side call to clear all fields.  Note that all the hidden fields
		 * are cleared as well.  Fields with 'noclear' attribute set to true
		 * are not cleared.
		 */
		clearFields: function()
		{
			var i=0, j=this.fieldInfo.length;
			for (;i<j;i++)
			{
				if (!this.fieldInfo[i].noclear)
				{
					this.setFieldValue(this.fieldInfo[i].fieldname, '');
				}
			}
		},

		/**
		 * Convenience method to hide a floating panel and clear all it's fields.
		 */
		dismiss: function()
		{
			this.hide();
			this.clearFields();
		},

		/**
		 * Clears all input controls that either have no display or are set to hidden visibility.
		 * This replaces the old clearUndisplayedFields in util.js and therefore needs to utilize
		 * the form element if there is one because the fields struct does not contain inputs
		 * that were not drawn using the Ctl custom tag.
		 */
		clearFieldsNotDisplayed: function()
		{
			var clearFieldNotDisplayed = function(fld, lookForSurrogates)
			{
				var vis = true, ele, eleTag, eleType, noclear, surrogateObj;

				ele = fld;
				while (ele.tagName.toLowerCase() != "body")
				{
					if (ele.style.display === "none" || ele.style.visibility === "hidden")
					{
						vis = false;
						break;
					}
					ele = ele.parentNode;
				}

				ele = fld;
				if (!vis)
				{
					noclear = ele.getAttribute("noclear");
					if (!noclear || noclear != "true")
					{
						eleTag = ele.tagName.toLowerCase();
						if (eleTag == "input")
						{
							eleType = ele.type.toLowerCase();
							if (eleType == "text" || eleType == "file" || eleType == "password")
							{
								ele.value = "";
							}
							else if (eleType == "checkbox" || eleType == "radio")
							{
								ele.checked = false;

								if (lookForSurrogates && eleType == "checkbox")
								{
									// look for surrogate VAL (value) object used by checkboxes and set its value
									surrogateObj=document.getElementById(ele.id+"_VAL");
									if (surrogateObj)
									{
										surrogateObj.value = '0';
									}
								}
							}
						}
						else if (eleTag == "textarea")
						{
							ele.value = "";
						}
						else if (eleTag == "select")
						{
							ele.selectedIndex = -1;
						}
					}
				}
			}, form, i;

			if (document.forms[this.form])
			{
				form = document.forms[this.form];
				for (i = 0; i < form.elements.length; i++)
				{
					clearFieldNotDisplayed(form.elements[i], false);
				}
			}
			else
			{
				for (i in this.fields)
				{
					clearFieldNotDisplayed(this.fields[i], true);
				}
			}
		},

		/**
		 * Sets a message in the form error block and displays it
		 * just like errors coming from the panel's process method.
		 */
		setFormError: function(errorMsg)
		{
			var errorBlock = document.getElementById(this.form + '_error_block'),
				formErrorDiv = document.getElementById(this.form + '_form_error');
			if (errorBlock && formErrorDiv)
			{
				formErrorDiv.innerHTML = errorMsg;
				errorBlock.style.display = 'block';
			}
		},

		/**
		 * Hides the form error block and any field errors it contains.
		 */
		hideFormError: function(errorMsg)
		{
			var errorBlock = document.getElementById(this.form + '_error_block');
			if (errorBlock)
			{
				errorBlock.style.display = 'none';
			}
		}

	});
} ());

