/* Client side JS delegate for the panel framework (see baseFields.cfc)
 * (#this.jss# is an instance of PanelController)
 *
 * Dependencies:
 *
 *	Before including panelbase.js, include EventManager.js
 */

/* Validated with http://jslint.com/ Edition 2010-10-16 */

/*jslint onevar: true, browser: true, evil: true, forin: true */
/*global DWREngine:false, trim:false,
base64Decode:false, applyEmbeddedCSS:false, applyEmbeddedJS:false,
util:false, window:false, alert:false*/

function PanelController(){}

(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;
	};

	// Convenience function to copy all props from object b into object a
	// and return object a
	// (similar but less sophisticated then jquery's $.extend or ExtJS's Ext.apply)
	function merge(a,b)
	{
		for (var i in b)
		{
			if (b.hasOwnProperty(i))
			{
				a[i]=b[i];
			}
		}
		return a;
	}

	// apply methods and properties to PanelController "class"
	merge(PanelController.prototype, {
		_hidden: true,
		_cloaked: false,
		_maskShown: false,
		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,
				stopDefault = function() {return false;}; // stop the default onRowSelected behavior

			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 =
						JSON.parse(f.finderfieldassociations);
				}
				// bind any found finder panels back to fields in this panel
				if (f.finderpanel && window[f.finderpanel])
				{
					if (f.findercloseonselect)
					{
						window[f.finderpanel].onRowSelected = function(f){
							return function(rowInfo) {
								that.onFind(rowInfo, f);
							};
						}(f);
					}
					else
					{
						window[f.finderpanel].onRowSelected = stopDefault;
					}
				}
			}
			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();},1);
			}
		},

		onBefore: function(action, f, dest, override)
		{
			if (!dest)
			{
				dest = this;
			}
			if (override === undefined || override)
			{
				EventManager.removePreListeners(action, dest.jss);
			}
			EventManager.addPreListener(action, function()
			{
				f.apply(dest,arguments);
			}, dest.jss);
		},

		onAfter: function(action, f, dest, override)
		{
			if (!dest)
			{
				dest = this;
			}
			if (override === undefined || override)
			{
				EventManager.removePostListeners(action, dest.jss);
			}
			EventManager.addPostListener(action, function()
			{
				f.apply(dest,arguments);
			}, dest.jss);
		},

		/* 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];
		},

		getFieldInfoArray: function(fieldName)
		{
			var i,j, out=[];

			for (i=0, j= this.fieldInfo.length; i<j; i++)
			{
				if (!fieldName || (this.fieldInfo[i].fieldname === fieldName))
				{
					out.push(this.fieldInfo[i]);
				}
			}
			return out;
		},

		getFieldArray: function(fieldName)
		{
			var i,j, out=[], ele;
			for (i=0, j= this.fieldInfo.length; i<j; i++)
			{
				if (!fieldName || (this.fieldInfo[i].fieldname === fieldName))
				{
					ele = document.getElementById(this.fieldInfo[i].domid);
					if (ele)
					{
						out.push(ele);
					}
				}
			}
			return out;
		},

		getFieldValueArray: function(fieldName)
		{
			var i,j, out=[], ele, inputType, surrogateObj, valPushed = false;
			for (i=0, j= this.fieldInfo.length; i<j; i++)
			{
				if (!fieldName || (this.fieldInfo[i].fieldname === fieldName))
				{
					ele = document.getElementById(this.fieldInfo[i].domid);
					if (ele)
					{
						inputType = ele.type.toLowerCase();
						if (inputType === "checkbox")
						{
							// look for surrogate VAL (value) object used by checkboxes and get its value
							surrogateObj=document.getElementById(ele.id+"_VAL");
							if (surrogateObj)
							{
								out.push(surrogateObj.value);
								valPushed = true;
							}
						}

						if (!valPushed)
						{
							out.push(ele.value);
						}
					}
				}
			}
			return out;
		},

		getFieldValue: function(fieldName)
		{
			return this.getFieldValueArray(fieldName).join();
		},

		/* 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 = merge({},this.request.parm);

			// intentionally keep request parm from overriding cfc name
			formData.cfc = this.cfc;

			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,origTarget;
			if (parms)
			{
				for (n in parms)
				{
					urlParms = urlParms + "&" + n + "=" + parms[n];
				}
			}
			this.clearFieldsNotDisplayed();
			origTarget = form.target;
			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 = origTarget;

			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 = "",
					form=document.forms[this.form],
					iframeName = "uploader" + (new Date()).getTime(),
					iframeDiv = document.createElement('div'),
					iframe,
					n,
					origTarget;

			if (parms)
			{
				for (n in parms)
				{
					urlParms = urlParms + "&" + n + "=" + parms[n];
				}
			}

			document.body.appendChild(iframeDiv);

			iframeDiv.innerHTML = "<iframe id='" + iframeName + "' name='" + iframeName + "' " +
				" src='about:blank' style='display:none;width:0;height:0' onload='window." +
				this.jss + ".fnUploadDirectCallback()'></iframe>";

			iframe = document.getElementById(iframeName);

			// callback that runs after iframe loads (created dynamically for closure)
			this.fnUploadDirectCallback = function()
			{
				this.clearSubmitLock();
				setTimeout(function(){document.body.removeChild(iframeDiv);}, 100);
				eval(base64Decode(trim(frames[iframeName].document.body.innerHTML)));
			};

			this.clearFieldsNotDisplayed();
			origTarget = form.target;
			form.action = this.ajaxUri + "?method=uploadCfc&cfc=" + this.cfc +
				urlParms;
			form.method = "post";
			form.target = iframeName;
			form.enctype = "multipart/form-data";
			form.encoding = "multipart/form-data";

			form.submit();
			form.target = origTarget;
			form.action = "";
			form.method = "";

			form.enctype = "text/plain";
			form.encoding = "text/plain";
		},

		fnUpdate: function(e, parms)
		{
			var waitMsg, formData = {}, n, button, rowInfo, source = this,
			fn = this.fnUpdateResponse;

			var tgt, eventTgt;

			// cross browser event normalization
			e=e||event;
			tgt=e.target||e.srcElement;

			eventTgt = tgt;

			// 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.getAttribute("rowidx") !== null)
			{
				rowInfo = this.getRowInfo(tgt);
			}

			if (!parms.action)
			{
				parms.action = "";
			}

			formData = getFormData.apply(this);
			formData.cfc=this.cfc;

			for (n in parms)
			{
				if (typeof(parms[n]) !== 'function')
				{
					formData[n] = parms[n];
				}
			}
			merge(formData, rowInfo);

			formData[eventTgt.name.replace('_EditField', '')] = eventTgt.value;
			rowInfo[eventTgt.name.replace('_EditField', '')] = eventTgt.value;

			EventManager.fireBeforeEvent(parms.action, formData, source);

			DWREngine._execute(this.ajaxUri + this.settings.noauditparm,
					null, "updateCfc", formData, function(resp)
					{
						resp.hasError = (resp.haserror === 'true');
						resp.tgt = eventTgt;
						delete resp.haserror;

						fn.apply(source, arguments);
						EventManager.fireAfterEvent(parms.action, formData, source, resp);
					});
		},

		fnUpdateResponse: function(resp)
		{
			var that=this;
			this.clearSubmitLock();
			if (resp.hasError)
			{
				this.addClass(resp.tgt, 'ass-GridEditorInputBox-Error');
				jQuery("<div style='padding-top:5px;'>"+resp.errors+'</div>').dialog(
				{
					autoOpen: true,
					title: 'Please correct the following errors',
					modal:true,
					buttons: {"OK": function() {jQuery(this).dialog("close");resp.tgt.focus();}}
				});
			}
			else
			{
				this.removeClass(resp.tgt, 'ass-GridEditorInputBox-Error');
				this.addClass(resp.tgt, 'ass-GridEditorInputBox-Success');
				setTimeout(
					function()
					{
						that.removeClass(resp.tgt, 'ass-GridEditorInputBox-Success');
					},
				500);
			}
		},

		getRowInfo: function(n)
		{
			var tbod, i, j, x, tr, d, sib, matched;

			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];
			}

			return d;
		},

		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);

				var that = this,
					titleBarId = '_' + this.form + 'TitleDiv_',
					panelTitlebar = jQuery('#' + titleBarId).addClass(this.settings.paneltitleclass),
					panelTitlebarButton,
					panelTitlebarButtonText,
					buttonNum = 0;

				if (this.settings.showreload)
				{
					panelTitlebarButton = jQuery('<a href="#"></a>')
						.addClass('ui-dialog-titlebar-button ui-corner-all')
						.css({'margin-right': buttonNum*20 + 'px'})
						.attr('role', 'button')
						.hover(
							function() {
								jQuery(this).addClass('ui-state-hover');
							},
							function() {
								jQuery(this).removeClass('ui-state-hover');
							}
						)
						.focus(function() {
							jQuery(this).addClass('ui-state-focus');
						})
						.blur(function() {
							panelTitlebarButton.removeClass('ui-state-focus');
						})
						.click(function(event) {
							that.load();
							return false;
						})
						.appendTo(panelTitlebar);

					panelTitlebarButtonText = jQuery('<span></span>')
						.addClass(
							'ui-icon ' +
							'ui-icon-refresh'
						)
						.text('Refresh')
						.attr('title', 'Refresh')
						.appendTo(panelTitlebarButton);

					buttonNum++
				}

				if (this.settings.collapsible)
				{
					panelTitlebarButton = jQuery('<a href="#"></a>')
						.addClass('ui-dialog-titlebar-button ui-corner-all')
						.css({'margin-right': buttonNum*20 + 'px'})
						.attr('role', 'button')
						.hover(
							function() {
								jQuery(this).addClass('ui-state-hover');
							},
							function() {
								jQuery(this).removeClass('ui-state-hover');
							}
						)
						.focus(function() {
							jQuery(this).addClass('ui-state-focus');
						})
						.blur(function() {
							jQuery(this).removeClass('ui-state-focus');
						})
						.click(function(event) {
							jQuery('#' + that.form + 'Cont').toggle();
							panelTitlebarButtonText = jQuery('#' + that.form + 'MinMax');
							if (panelTitlebarButtonText.hasClass('ui-icon-minusthick'))
							{
								panelTitlebarButtonText
									.text('Expand')
									.attr('title', 'Expand')
									.removeClass('ui-icon-minusthick')
									.addClass('ui-icon-newwin');
							}
							else
							{
								panelTitlebarButtonText
									.text('Collapse')
									.attr('title', 'Collapse')
									.removeClass('ui-icon-newwin')
									.addClass('ui-icon-minusthick');
							}
							return false;
						})
						.appendTo(panelTitlebar);

					panelTitlebarButtonText = jQuery('<span></span>')
						.addClass(
							'ui-icon ' +
							'ui-icon-minusthick'
						)
						.attr('id', that.form + 'MinMax')
						.text('Collapse')
						.attr('title', 'Collapse')
						.appendTo(panelTitlebarButton);

					buttonNum++;
				}

				this.clearSubmitLock();
			}
			else
			{
				document.getElementById(this.form + "Div").innerHTML = '';
				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)
		{
			if (!this._maskShown)
			{
				this._maskShown = true;
				jQuery("#"+this.form+"Cont").mask(msg, 10);
			}
		},

		hideWaitMsg: function()
		{
			if (this._maskShown)
			{
				jQuery('#'+this.form+'Cont').unmask();
				this._maskShown = false;
			}
		},

		// private method to really do the show
		_show: function(parms)
		{
			var title, that;

			if (this.settings.float)
			{
				if (parms && 'title' in parms)
				{
					title = parms.title;
				}
				else if (this.settings.title)
				{
					title = this.settings.title;
				}
				else
				{
					title='';
				}

				if (!this.popupWin && !this.popupWinFuture)
				{
					that = this;

					// have to do this after a slight delay in case it was
					// triggered by a panel display method which shifted the dom around (and perhaps recreated
					// the div that the dialog is (or will be) based on

					// why this wasn't needed in ExtJS is slightly boggling my brain
					this.popupWinFuture = setTimeout(function()
					{
						var ht = 'auto';
						if (that.settings.floatdynaheight > 0)
						{
							ht = jQuery(window).height() * (that.settings.floatdynaheight/100);
						}
						var wd = 50 + that.settings.floatwidth;
						if (that.settings.floatdynawidth > 0)
						{
							wd = jQuery(window).width() * (that.settings.floatdynawidth/100);
						}
						var sel = '#' + that.form + 'Cont',
							options = {
								modal: true,
								closeOnEscape: that.settings.closeonescape,
								resizable: that.settings.floatresizable,
								title:  title,
								height: ht,
								minHeight: 'auto',//auto is not a supported value for minHeight, but setting this value makes the dialog render with the right height in IE6
								width: wd,
								minWidth: 50 + that.settings.floatwidth
							};

						jQuery(sel).dialog(options);

						jQuery(sel).bind("dialogbeforeclose",function(event,ui) {
							that.fireBeforeEvent('dialogbeforeclose', that);
							that.hide();
							return false;
						});

						jQuery(sel).bind("dialogresize",function(event,ui) {
							that.dialogresize(event,ui);
							return false;
						});

						that.popupWinFuture = undefined;
						that.popupWin = true;
						that.fireAfterEvent('show');
					},0);
				}
			}
			else
			{
				if (this.panelDisplayLatch === false)
				{
					this.panelDisplayLatch = true;
					this.load();
				}

				jQuery('#' + this.form + '_panel_holder').show();
				this.fireAfterEvent('show');
			}
		},

		// private method to really do the hide
		_hide: function()
		{
			var sel = '#' + this.form + 'Cont';

			if (this.settings.waitmsgactive)
			{
				this.hideWaitMsg();
			}
			if (this.settings.float)
			{
				if (this.popupWinFuture)
				{
					clearTimeout(this.popupWinFuture);
					this.popupWinFuture = undefined;
				}
				if (this.popupWin)
				{
					jQuery(sel).dialog('destroy');
					this.popupWin = false;
					this.fireAfterEvent('hide', this);
				}
			}
			else
			{
				jQuery('#' + this.form + '_panel_holder').hide();

				this.fireAfterEvent('hide', this);
			}
		},

		/*
			Show the panel.
		*/
		show: function(parms)
		{
			if (!this._hidden)
			{
				return;
			}
			this._hidden = false;
			if (this._cloaked)
			{
				return;
			}
			this._show(parms);
		},

		/* hide the panel */
		hide: function()
		{
			if (this._hidden)
			{
				return;
			}
			this._hidden = true;
			if (this._cloaked)
			{
				return;
			}
			this._hide();
		},

		// cloak & uncloak is an alternative way to show & hide the panel
		// the main difference is that a panel that is cloaked
		// won't show, even when show is called. (you have to call uncloak to show it)

		// this is useful for when a panel should be hidden or shown based on a business logic
		// condition.

		// I know this is a bit twisted, but it's necessary in order to
		// have a panel stay hidden when the group manager is hiding/showing things
		// as part of regular history or breadcrumbs activity

		cloak: function()
		{
			if (this._cloaked)
			{
				return;
			}
			this._cloaked = true;
			if (!this._hidden)
			{
				this._hide();
			}
		},

		uncloak: function()
		{
			if (!this._cloaked)
			{
				return;
			}
			this._cloaked = false;
			if (!this._hidden)
			{
				this._show();
			}
		},

		reposition: function()
		{
			var sel = '#' + this.form + 'Cont';
			if (this.popupWin)
			{
				jQuery(sel).dialog('option', 'position', 'center');
			}
		},

		dialogresize: function(event,ui)
		{
		},

		addClass: function(domElement, className)
		{
			jQuery(domElement).addClass(className);
		},

		removeClass: function(domElement, className)
		{
			jQuery(domElement).removeClass(className);
		},

		onKeyPress: function(e)
		{
			if(e.which == 13 || e.keyCode == 13)
			{
				var button = document.getElementById(this.settings.clickbtnidonenter);
				if (button)
				{
					button.click();
				}
				e.keyCode = 0;
				return false;
			}
		},

		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 = tr;
				this.addClass(tr,'ass-FinderRowSelected');

				// save info related to selected row in a nice little package so
				// it can be used later.
				this.finderSelectedRowInfo =
					merge({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);
				}

				this.fireBeforeEvent('rowselected', this.finderSelectedRowInfo);
				if (this.onRowSelected(this.finderSelectedRowInfo))
				{
					this.fnSubmit(
						merge({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)
					);
				}
				this.fireAfterEvent('rowselected', this.finderSelectedRowInfo);
				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.removeClass(this.finderSelectedRow,'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
		},

		/**
		 * Add a textlist item
		 */
		addItemToTextlist: function(rowInfo, fieldInfo)
		{
			var i,
					assoc=fieldInfo.finderfieldassociations,
					keyAlreadyAdded = false,
					fieldName,
					curKeyStr = '',
					addedKey,
					currKeyArray,
					addedValue,
					curValue;

			this.fireBeforeEvent('textlistitemadded', rowInfo);

			if (assoc)
			{
				for (i in assoc)
				{
					if (!keyAlreadyAdded)
					{
						if (i in this.fields && assoc[i] && assoc[i] in rowInfo.rowData)
						{
							fieldName = this.fields[i].name;
							if (assoc[fieldName] == assoc[fieldInfo.keyfield])
							{
								addedKey = rowInfo.rowData[assoc[i]];
								addedKey = addedKey.replace(",", "&#44;");
								curKeyStr = this.fields[i].value;

								currKeyArray = curKeyStr.split(',');

								if (jQuery.inArray(addedKey, currKeyArray) >= 0)
								{
									keyAlreadyAdded = true;
								}
								else
								{
									if (curKeyStr === '')
									{
										curKeyStr = addedKey;
									}
									else
									{
										curKeyStr = curKeyStr + "," + addedKey;
									}
								}
							}
						}
					}
				}

				for (i in assoc)
				{
					if (!keyAlreadyAdded)
					{
						if (i in this.fields && assoc[i] && assoc[i] in rowInfo.rowData)
						{
							addedValue = rowInfo.rowData[assoc[i]];
							addedValue = addedValue.replace(",", "&#44;");
							curValue = this.fields[i].value;

							if (curValue === '')
							{
								curValue = addedValue;
							}
							else
							{
								curValue = curValue + ',' + addedValue;
							}

							this.setFieldValue(i, curValue, curKeyStr, fieldInfo);
						}
					}
				}
			}

			this.fireAfterEvent('textlistitemadded', rowInfo);

			return false;
		},

		/**
		 * 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.
		 *
		 * key and fieldInfo parameters are optional
		 */
		setFieldValue: function(fieldName, value, key, fieldInfo)
		{
			var field = this.fields[fieldName],surrogateObj,surrogateTLObj,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));
				}

				// Look for surrogate TL (textlist display) object and set its value
				surrogateTLObj=document.getElementById(field.id+"_TL");
				if (surrogateTLObj && fieldInfo)
				{
					this.buildTextList(surrogateTLObj, fieldName, fieldInfo.keyfield, key, value);
				}
			}
		},

		/**
		 * Builds a text list.
		 */
		buildTextList: function(surrogateTLObj, valueField, keyField, key, value) {
			util.removeAllChildren(surrogateTLObj);
			var i,val,valArray = value.split(','),keyArray = key.split(',');

			for (i=0;i<valArray.length;i++)
			{
				val = valArray[i];
				val = val.replace("&#44;", ",");
				this.addTextListItem(surrogateTLObj, valueField, keyField, keyArray[i], val);
			}
			return surrogateTLObj;
		},

		/**
		 * Adds a text list item
		 */
		addTextListItem: function(surrogateTLObj, valueField, keyField, key, val) {
			var buttonHolder,
				itemHolder,
				textHolder,
				textNode,
				actionHolder,
				deleteLink,
				delItem,
				delImg,
				that=this;

			buttonHolder = jQuery('<div style="margin-bottom:5px; float:left;"></div>');
			textHolder = jQuery('<button>' + val + '</button>');
			actionHolder = jQuery('<button class="ass-textlist-item-button-' + surrogateTLObj.id + '" title="Click to Remove">Click to Remove</button>');

			buttonHolder[0].appendChild(textHolder[0]);
			buttonHolder[0].appendChild(actionHolder[0]);
			surrogateTLObj.appendChild(buttonHolder[0]);

			jQuery(textHolder)
			.button({
				disabled: true
			})
			.hover(function() {
				jQuery(this).removeClass('ui-state-hover');
			})
			.next()
				.button({
					icons: {
						secondary: 'ui-icon-circle-close'
					},
					text: false
				}).click(function(e) {
					that.onTextlistItemDeleteClicked(e);
				}).attr("key", key)
				.attr("keyField", keyField)
				.attr("valueField", valueField)
				.attr("form", this.form)
				.parent()
					.buttonset();
			jQuery(textHolder).removeClass("ui-state-disabled")
				.removeAttr("disabled");
		},

		/**
		 * Triggered when a textlist item is clicked.
		 *
		 * Removes the textlist item visual component, and removes the entry
		 * from the textlist's associated keyfield.  Does this for all duplicate versions
		 * of the textlist.
		 */
		onTextlistItemDeleteClicked: function(e)
		{
			var deleteButton,
				key,
				keyField,
				valueField,
				that = this;

			e=e||event;
			deleteButton=e.target||e.srcElement;
			if (deleteButton.getAttribute("key") == null)
			{
				deleteButton = deleteButton.parentNode;
			}
			key = deleteButton.getAttribute("key");
			keyField = deleteButton.getAttribute("keyField");
			valueField = deleteButton.getAttribute("valueField");

			jQuery('button[key="' + key + '"][keyField="' + keyField + '"][valueField="' + valueField + '"]').each(
				function(index, textListItemToBeDeleted)
				{
					that.deleteTextListItem(textListItemToBeDeleted);
				}
			);
		},

		deleteTextListItem: function(deleteButton)
		{
			var tlObj,
				form,
				key,
				keyField,
				valueField,
				keyIds,
				keyArr,
				keyidx,
				i,
				vals,
				valArr,
				elem;

			tlObj = deleteButton.parentNode;
			key = deleteButton.getAttribute("key");
			form = deleteButton.getAttribute("form");
			keyField = form + deleteButton.getAttribute("keyField");
			valueField = form + deleteButton.getAttribute("valueField");
			elem = document.getElementById(keyField);

			if (elem != undefined)
			{
				keyIds = elem.value;
				keyArr = keyIds.split(',');
				keyidx = -1;

				for (i=0;i<keyArr.length;i++)
				{
					if (keyArr[i] == key)
					{
						keyidx = i;
						break;
					}
				}

				if (keyidx != -1)
				{
					this.fireBeforeEvent('textlistitemdeleted', keyidx);

					vals = document.getElementById(valueField).value;
					valArr = vals.split(',');
					valArr.splice(keyidx,1);
					keyArr.splice(keyidx,1);

					keyIds = keyArr.join(",");
					document.getElementById(keyField).value = keyIds;

					vals = valArr.join(',');
					document.getElementById(valueField).value = vals;

					this.fireAfterEvent('textlistitemdeleted', keyidx);
				}

				if (tlObj != 'undefined' && tlObj.parentNode && tlObj.parentNode.removeChild)
				{
					tlObj.parentNode.removeChild(tlObj);
				}
			}

			return false;
		},

		/**
		 * 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 that=this,clearFieldNotDisplayed = function(fld)
			{
				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 (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 (eleType == "hidden")
							{
								//If Version is specified and it is >= 3, process it
								if (that.settings.version >= 3 && !util.strEndsWith(ele.id, "_VAL"))
								{
									ele.value = "";
								}
								surrogateObj=document.getElementById(ele.id+"_TL");
								if (surrogateObj)
								{
									util.removeAllChildren(surrogateObj);
								}
							}
						}
						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]);
				}
			}
			else
			{
				for (i in this.fields)
				{
					clearFieldNotDisplayed(this.fields[i]);
				}
			}
		},

		/**
		 * 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';
			}
		},

		/**
		 * Expands (shows) the panel's container div below the title div
		 */
		expand: function()
		{
			var container = document.getElementById(this.form + 'Cont'), img;
			if (container)
			{
				container.style.display = 'block';
				img = document.getElementById('_' + this.form + 'ExpColImg_');
				if (img)
				{
					img.src = '/resources/common/images/icons/bullet_toggle_minus.png';
					img.title = 'Collapse';
				}
			}
		},

		/**
		 * Collapses (hides) the panel's container div leaving only the title div showing
		 */
		collapse: function()
		{
			var container = document.getElementById(this.form + 'Cont'), img;
			if (container)
			{
				container.style.display = 'none';
				img = document.getElementById('_' + this.form + 'ExpColImg_');
				if (img)
				{
					img.src = '/resources/common/images/icons/bullet_toggle_plus.png';
					img.title = 'Expand';
				}
			}
		},

		/**
		 * Expands (shows) the panel's container div below the title div
		 */
		expandCollpseToggle: function()
		{
			var container = document.getElementById(this.form + 'Cont'), img;
			if (container)
			{
				img = document.getElementById('_' + this.form + 'ExpColImg_');
				if (container.style.display === 'none')
				{
					container.style.display = 'block';
					if (img)
					{
						img.src = '/resources/common/images/icn_minimize.png';
						img.title = 'Collapse';
					}
				}
				else
				{
					container.style.display = 'none';
					if (img)
					{
						img.src = '/resources/common/images/icn_maximize.png';
						img.title = 'Expand';
					}
				}
			}
		}

	});
}());

