Y = YAHOO;
DOM = Y.util.Dom;
EV = Y.util.Event;
ANIM = Y.util.Anim;
ISUD = Y.lang.isUndefined;
ISVAL = Y.lang.isValue;
WAIT_BAR = 'gimages/waitbar.gif';
MODULE_WAIT = '<img src="gimages/wait.gif"/>';
CONERR = "There has been an error communicating with the server.  We are sorry but we can not continue.  Please try again later.";
function ge(id) { return DOM.get(id); }
function log(msg) { if(!(typeof console === 'undefined')) console.log(msg); }

/* This is the now standard javascript file to include with all Yokohama-based */
/* Solutions which extend or mimic the basic business framework.  First        */
/* include the yuiloader-dom-event/yuiloader-dom-event.js file, then the anim */
/* library, then this one.  This is an extension of method #2 from                  */
/* http://yuiblog.com/blog/2008/10/17/loading-yui/                                */
/* Which means we get Yahoo, DOM, Event, anim, and Loader		                */
/* alternately, you can use utilities.js, which includes yahoo, dom, event, connection, */
/*  animation, dragdrop, element, get, & yuiloader - it is 116.7k  */

var BLUE = function()
{
	var m = {
		timerRunning : false,
		timerId : 0,
		divNum : 1,
		panels : [],
		forms : [],
		cpanel : null
	}; // return object

	// create a new div element with the specified content, set the CSS class as specified, append it to the parent and return it.
	function ad(parent,cn,content)
	{
		var div = document.createElement('div');
		div.setAttribute('class', cn);
		div.innerHTML = content;
		parent.appendChild(div);
		return div;
	}

	// Creates (or returns if was previously created) a "panel" - which may be a dialog box, or simply
	// information - but it is generally modal, with a close button, a title, and body text.
	// panel is saved at BLUE.cpanel;
	// methods on the panel object are:
	//		init(cf) - used internally to initialize the panel
	//			cf.modal : sets if the panel is modal or not
	//			cf.title : Title of the panel
	//			cf.body : Body of the panel
	//			cf.buttons : An array of button objects  { text:"Close", isDefault: true, handler: cp.close, type:'p' }
	//				button object properties:
	//					button.text : button text
	//					button.isDefault : true/false
	//					button.handler : callback for this button
	//					button.type : p/n (for styling)
	//		initButtons(buttons) - initialize the button panel with the button object
	//		disableButtons() - disables the button panel (useful during communication with server)
	//		close() - closes the panel
	//		show() - displays the panel - must be called to display the panel
	//		enableButtons - if buttons were disabled, this re-enables them
	//		showWait - displays the wait "busy icon"
	//		hideWait - hides the wait "busy icon"
	//		setBody - sets the body HTML
	//		setTitle - sets the title
	function createPanel(cf)
	{
		if(m.cpanel != null)
		{
			m.cpanel.init(cf);
			return m.cpanel;
		}

		m.cpanel = {};
		var cp = m.cpanel;  // shorter reference. ;-)

		var pe = document.createElement('div');
		pe.setAttribute('id','cpanel');
		DOM.setStyle(pe,'display','none');
		document.getElementsByTagName("body")[0].appendChild(pe);

		var me = document.createElement('div');
	    me.setAttribute('id','dialogmask');
	    DOM.setStyle(me,'display','none');
	    DOM.setStyle(me,'zoom',1);
	    document.getElementsByTagName("body")[0].appendChild(me);

		/* Initialize the cpanel with the given attributes.  Currently supports the following:
		 *    title : Title of the dialog box (else blank)
		 *    body : Body content of dialog (else blank)
		 *    buttons : Buttons of the dialog (else just a close button)
		 */
		cp.init = function(cf)
		{
			cf = cf || { };  // if no init is passed, use a blank

			cp.modal = cf.modal || false;

			if(cf.width != null)
			{
				DOM.setStyle('cpanel','width','' + cf.width + 'px');
				DOM.setStyle('cpanel','margin-left','' + Math.floor(0-cf.width/2) + 'px');
			}

			var title = cf.title || '';
			var body = cf.body || '';

			// Now set the HTML content of the panel
			pe.innerHTML = '';
			cp.hd = ad(pe,'hd',title);
			cp.bd = ad(pe,'bd',body);
			cp.ft = ad(pe,'ft','');

			cp.initButtons(cf.buttons);
		};

		cp.initButtons = function(buttons) {
			buttons = buttons || [ { text:"Close", isDefault: true, handler: cp.close, type:'p' }];
			cp.buttons = buttons;

			// create the HTML to place in the footer area..
			var footer = "<div class='buttons'>";
			for(var i = 0;i<buttons.length;i++)
			{
				var b = buttons[i];
				footer += "<a id='cpanelb" + i + "' href='' class='" + b.type + "' onClick='return false;'>" + b.text + "</a>"
			}
			footer += "</div>";

			cp.ft.innerHTML = footer;

			// now setup the events..
			for(i = 0;i<buttons.length;i++)
			{
				b = buttons[i];
				var handler = function(b) { //nm
						return function(e) {
							b.handler(); EV.stopEvent(e); };
						}(b);
				b.el = DOM.get('cpanelb' + i);  // hold on to the buttons element object
				EV.on(b.el,'click', handler );
				/*if(b.isDefault)  was breaking on IE for t4bp congrats dialog
					b.el.focus();*/
				if(b.key != null)
					EV.on(cp,'keydown', function(b,handler) { return function(e) {  if(e.keyCode == b.key) handler(); } }(b,handler));
			}
		};

		cp.disableButtons = function()
		{
			// Ghost out the buttons...
			for(var i=0;i<cp.buttons.length;i++)
			{
				var b = m.cpanel.buttons[i];
				EV.purgeElement(b.el);  // remove all events.. will need to reset buttons to work again..
				DOM.setStyle(b.el,'opacity',.25);
			}
		};

		cp.close = function() {
			EV.purgeElement(pe,true); // remove all event listeners associated with this element and it's children
				var a = new ANIM(pe, { opacity: { from: .9, to:0 }}, .2);
				a.onComplete.subscribe(function() { DOM.setStyle(pe,'display','none'); });
				a.animate();
				if(cp.modal)
				{
					a = new ANIM('dialogmask', { opacity: { from: .4, to:0 }}, .2);
					a.onComplete.subscribe(function() { DOM.setStyle(me,'display','none'); });
					a.animate();
				}
			 };

		// fade in the panel...  (usually want to first define buttons/title/content
		cp.show = function()
		{
			DOM.setStyle(pe,'opacity',0);
			DOM.setStyle(pe,'display','block');
			if(cp.modal)
			{
			    DOM.setStyle(me,'opacity',0);
			    DOM.setStyle(me,'display','block');
			}

			// Animate the dialog box coming in...
			var a = new ANIM(pe, { opacity: { from: 0, to:.9 }});
			a.animate();
			if(cp.modal)
			{
				a = new ANIM(me, { opacity: { from: 0, to:.7 }});
				a.animate();
			}
		};

		cp.enableButtons = function() { cp.initButtons(cp.buttons); }; //nm

		cp.showWait = function() { cp.wait = ad(pe,'wait',''); }; //nm
		cp.hideWait = function() { DOM.setStyle(cp.wait,'display','none'); } //nm

		cp.setBody = function(b) { cp.bd.innerHTML = b; }; //nm
		cp.setTitle = function(b) { cp.hd.innerHTML = b; }; //nm

		cp.init(cf);
		return cp;
	}

	/* When an element is populated dynamically, this is run afterwards for init */
	function elementLoad(divElm)
	{
		enableRichTextInt(divElm);
	};

	/* When an element is to be re-populated with dynamic content, call this first */
	function elementUnload(divElmId)
	{
	};

	/* Scan all text areas within the element and switch to rich text editing for all richtext="true" */
	function enableRichTextInt(divElm)
	{
		var divElm = DOM.get(divElm);

		var nodeList = divElm.getElementsByTagName("textarea");

		for (var i=0; i<nodeList.length; i++)
		{
			var elm = nodeList.item(i);
			if(elm.getAttribute('richtext') == "true")
				richenTAint(elm);
		}
	};

	/**
	*	Run any new <script> elements that are dynamically loaded...  pass in the element
	*  id, and this scans the id looking for script elements, which it then evalutates.
	*  Is this dangerous?  It is if you call it on untrusted content.  It is basically the same
	*  as loading a page to begin with, which executes all javascript contained...
	*/
	function executeScripts(divElmId)
	{
		var divElm = ge(divElmId);
		var nodeList = divElm.getElementsByTagName("script");
		for (var i=0; i<nodeList.length; i++)
		{
			var elm = nodeList.item(i);
			/*if(elm.getAttribute('src') != undefined)
				methods.loadjs(elm.getAttribute('src'), new function() { } );
			else*/
				eval(elm.innerHTML);
		}
	};

	/* Obtains the named configuration from the editorConfMap.  This supports the 'extend'
		parameter which extends another named configuration.  This allows "chaining" of any
		length */
	function getConf(name)
	{
		var conf = { };
		if(!ISUD(editorConfMap[name]))
		{
			var nconf = editorConfMap[name];
			if(!ISUD(nconf.extend))
				conf = getConf(nconf.extend);
			conf = Y.lang.merge(conf,nconf);
		}
		return conf;
	}

	// pad to 2 digits
	function pad(n)
	{
		if(n > 9)
			return n;
		return "0" + n;
	};

	/* This requests HTML from the server and populates a div with the results */
	/* NOTE: If you also have a div w<divid> then that element gets populated with */
	/* A wait.gif image during the load process.  This allows you to have a busy  */
	function requestInternal(url,elementId,cb)
	{
		if(elementId)
		{
			elementUnload(elementId);

			var welm = ge('w' + elementId.substr(2));

			if(welm != null)
				welm.innerHTML = MODULE_WAIT;
		}

		var responseSuccess = function(o) {
			if(elementId)
			{
				if(welm != null)
					welm.innerHTML = '';
				ge(elementId).innerHTML=o.responseText;
				ge(elementId).style.display = "block";
				elementLoad(elementId);
				executeScripts(elementId);
			}

			if(cb)
				cb();
		};

		var responseFailure = function(o) {
			if(welm != null)
				welm.innerHTML = '';
			if(elementId)
				ge(elementId).innerHTML = '<center>&lt;Error&gt;</center>';
			alert('We are sorry, there was a problem with the request.');
		}

		var callback = {
			success : responseSuccess,
			failure : responseFailure};

		var transaction = Y.util.Connect.asyncRequest('GET', url, callback, null);

		//ge(elementId).scrollIntoView();
	};

	editorConfMap = {
			'base' : {
				dompath: false,
				animate: true,
				markup: "xhtml",
				filterWord: true,
				handleSubmit: true
			},
			'simple' : {
				extend : 'base',
				toolbar: {
					buttons: [
						{ group: 'textstyle', label: 'Font Style',
							buttons: [
								{ type: 'push', label: 'Bold CTRL + SHIFT + B', value: 'bold' },
								{ type: 'push', label: 'Italic CTRL + SHIFT + I', value: 'italic' },
								{ type: 'push', label: 'Underline CTRL + SHIFT + U', value: 'underline' }
										]
						},
						{ group: 'alignment', label: 'Alignment',
							buttons: [
								{ type: 'push', label: 'Align Left CTRL + SHIFT + [', value: 'justifyleft' },
								{ type: 'push', label: 'Align Center CTRL + SHIFT + |', value: 'justifycenter' },
								{ type: 'push', label: 'Align Right CTRL + SHIFT + ]', value: 'justifyright' },
								{ type: 'push', label: 'Justify', value: 'justifyfull' }
							]
						},
						{ group: 'indentlist', label: 'Indenting',
							buttons: [
								{ type: 'push', label: 'Indent', value: 'indent', disabled: true },
								{ type: 'push', label: 'Outdent', value: 'outdent', disabled: true },
								{ type: 'push', label: 'Create an Unordered List', value: 'insertunorderedlist' },
								{ type: 'push', label: 'Create an Ordered List', value: 'insertorderedlist' }
							]
						}
					]
				}
			},
			'htmlPrivate' : {
				extend : 'base'
			}
		};

	// this is here for IE7, to force a width/height, as otherwise the editor can't be focused on (click inside, but no cursor)
	if(Y.env.ua.ie > 0) { editorConfMap.base.width = "440px";  editorConfMap.base.height = "200px"; }
	// silly safari is playing the same game...  but with just height.
	if(Y.env.ua.webkit > 0) { editorConfMap.base.height = "200px"; }

	/* Richen the TextArea specified.  The configuration used will be determined as follows:
			First obtain the 'defConf' configuration (which can be overridden via mapEditorConf('defConf' , defConf) )
			Then, the classes are scanned of the text area, and if any match a mapped configuration name, that
			configuration is "appended" - then finally if an xconf is passed in, it is appended. */
	function richenTAint(elm,xconf)
	{
		var config = { };

		var myelm = ge(elm);

		var ca = myelm.className.split(" ");  // lets examine the classes for this textarea
		for(c in ca)
			if(!ISUD(editorConfMap[ca[c]]))  // if the class has a corresponding config entry..
				config = Y.lang.merge(config,getConf(ca[c])); // ... blend it into the current config

		if(!ISUD(xconf))
			config = Y.lang.merge(config,xconf);

		if((myelm.className == "") && (ISUD(xconf)))
			config = getConf('simple');

		BLUE.run("editor,resize", function()
			{
				var myEditor = new Y.widget.Editor(myelm.id, config);
				if(ISVAL(config.editorCSS))
				{
					myEditor.on('editorContentLoaded', function() {
							var head = this._getDoc().getElementsByTagName('head')[0];
							var link = this._getDoc().createElement('link');
							link.setAttribute('rel', 'stylesheet');
							link.setAttribute('type', 'text/css');
							link.setAttribute('href', config.editorCSS);
							head.appendChild(link);
						}, myEditor, true);
				}

				window[myelm.id + "_editor"] = myEditor;

				if(myelm.className == "htmlPrivate") /* This is temporary - to insert an HTML edit button */
				{
					myEditor.on('toolbarLoaded', function()
						{
							//Simple button config
							var button = {
								type: 'push',
								label: 'Edit HTML',
								value: 'editHTML',
								disabled: false
							};

							this.toolbar.addButtonGroup({ group: 'extra', label: 'Extra Tools', buttons: [] });
							this.toolbar.addButtonToGroup(button,'extra');

							this.toolbar.on('editHTMLClick', function(o)
								{
									myEditor.saveHTML();

									ad(document.getElementsByTagName("body")[0],'htmlEditWin','<textarea id="ta" style="width: 592px; height: 465px;">' + myelm.value + '</textarea>' +
											'<div style="text-align: right"><button value="Cancel" id="cancel">Cancel</button><button value="Save" id="save">Save</button></div>');

									EV.on(['save','cancel'],'click',function(e)
										{
											var ta = ge('ta');
											ta.parentNode.parentNode.removeChild(ge('ta').parentNode);
											if(this.id != 'cancel')
												myEditor.setEditorHTML(ta.value);
										});

								}, myEditor, true);

						}, myEditor, true);
				}

				myEditor.render();
			});
	}

	// -------------- BLUE methods start here  ---------

	// ----------- Panel stuff

	/*
	 Displays a nicer looking alert box.  params:
		header: head area content (title, etc.) (may contain html)
		text : content of alert
		panelWidth: optional panel width override
		*/
	m.alert = function(header,text)
	{
		BLUE.showDialog({title: header, body: text})
	};

	//	Base public function for showing a dialog.  Assumes you have configured the buttons, title, etc.
	//	If you already have a panel/dialog, simply call show() on it.
	//	To style, use the #cpanel to decorate...
	m.showDialog = function(cf)
	{
		if(cf.modal == null)
			cf.modal = true;
		createPanel(cf).show();
	};

	// get default buttons for use in a panel with a form as primary content.
	// returns buttons array object with 2 buttons ("Submit" and "Cancel".  Submit submits first found form.
	// ex: BLUE.showPanel("http:myurl.com", { title: 'Form Test", buttons: BLUE.getDefFormButtons() });
	// U
	m.getDefFormButtons = function()
	{
		return 	[
			{ text:"Cancel", handler: BLUE.closePanel,type:'n', key: 27 },
			{ text:"Submit", handler:  function(e) {
					var form =DOM.get('cpanel').getElementsByTagName("form")[0];
					if(BLUE.forms[form.id] != null)
					{
						if(!BLUE.forms[form.id].subfunc(e))
							return; // if the form doesn't validat, exit...'
					}

					form.submit();
				}, type: 'p'
			}
		];
	};

	// get default buttons for use in a panel with a form as primary content.
	// returns buttons array object with 2 buttons ("Submit" and "Cancel".  Submit submits first found form.
	// ex: BLUE.showPanel("http:myurl.com", { title: 'Form Test", buttons: BLUE.getDefFormButtonsAjax() });
	// This submits the form via AJAX post, expecting the result to be in JSON format.  Will
	// display a wait indicator until it is returned, then clear the dialog and display JSON.title and JSON.content
	m.getDefFormButtonsAjax = function()
	{
		return 	[
			{ text:"Cancel", handler: BLUE.closePanel,type:'n', key: 27 },
			{ text:"Submit", handler:  function(e) {
					var form =DOM.get('cpanel').getElementsByTagName("form")[0];
					if(BLUE.forms[form.id] != null)
					{
						if(!BLUE.forms[form.id].subfunc(e))
							return; // if the form doesn't validat, exit...'
					}

					BLUE.cpanel.disableButtons();
					BLUE.cpanel.showWait();
					BLUE.ajaxPost(form, function(res)  // our callback
						{
							BLUE.cpanel.initButtons(); // this will reinitialize the buttons with just a close button...
							BLUE.cpanel.hideWait();
							if(res.title)
								BLUE.cpanel.setTitle(res.title);
							BLUE.cpanel.setBody(res.content);
						}
					);
				}, type: 'p'
			}
		];
	};

	m.hidePanel = function(divId)
	{
		//ge(divId + '_c').style.visibility = "hidden";
		//ge(divId + '_mask').style.display = "none";
		panel.destroy();
	};

	m.cancelPanel = function(id)
	{
		this.panels[id].hideMask();
		this.panels[id].hide();
		//this.panels[id] = null;
	};

	// Closes the global panel.  Same as BLUE.cpanel.close()
	m.closePanel = function()
	{
		BLUE.cpanel.close();
	};

	// Takes an existing div (usually hidden) and turns it into a Yahoo Panel.  Probably
	// should convert to using our panel stuff.
	m.panelize = function(divId,title,panelWidth)
	{
		if(true) // temporary - testing with our panel - but consider LIFECYCLE, etc!
		{
			m.alert(title,ge(divId).innerHTML);
			return;
		}

		if(ge(divId + '_c') == undefined) // don't allow multiple instances
		{
			BLUE.run("container", function() {

				// default the panelWidth to 400px
				if (panelWidth == null)
					panelWidth=400;

				panel = new Y.widget.Panel(divId, { close: true, visible: true, width:""+panelWidth+"px", fixedcenter:true, modal: true, zindex: 100, constraintoviewport:true});

				panel.setHeader(title);
				//p.hideEvent.subscribe(function() { p.destroy(); } );
				var divElm = ge(divId);
				divElm.style.display = "block";
				panel.render(document.body);
				//panels[divId] = p;

				//	focus on the first of any subforms
				var divForms = divElm.getElementsByTagName("form");
				if (divForms.length > 0)
					divForms[0].elements[0].focus();
			});
		}
		else
		{
			ge(divId + '_c').style.visibility = "visible";
			ge(divId + '_mask').style.display = "block";
		}
	};

	// Displays a panel with content obtained from a module service (specified in the URL)
	// Buttons default to the standard form buttons for ajax.  Which means the form is submitted
	// over ajax, and the response from THAT form is finally displayed in the dialog. (see getDefFormButtonsAjax)
	m.showPanel = function(url,cf)
	{
		BLUE.run("connection", function() {

			cf.body = '<div style="text-align: center; margin-top: 40px;"><img src="' + WAIT_BAR + '"/></div>';
			if(cf.modal == null)
				cf.modal = true;

			if(cf.buttons == null)
				cf.buttons = BLUE.getDefFormButtonsAjax();

			BLUE.showDialog(cf);

			/* Now, configure the callback, call back to the server for the content, and populate the dialog */
			var callback =
			{
				display : function(html) //nm
				{
					BLUE.cpanel.bd.innerHTML = html;
					elementLoad('cpanel');
					executeScripts('cpanel');
					if(cf.cb != null)
						cf.cb();
				},

				success : function(o) //nm
				{
					this.display(o.responseText);
				},

				failure : function(o) //nm
				{
					BLUE.cpanel.bd.innerHTML = CONERR + "(" + o.statusText + ")";
				},
				timeout : 30000   // 30 seconds..
			};

			Y.util.Connect.asyncRequest('GET', url, callback, null);
		});
	};

	// ------ Form Stuff

	/*
	* Post the form via ajax.  Will call the cb when done - cb will be passed the results of the JSON object returned from the post.
	*/
	m.ajaxPost = function(form,cb)
	{
		BLUE.run("connection", function() {
			/* Now, configure the callback, call back to the server for the content, and populate the dialog */
			var callback =
			{
				sendResponse : function(o)  //nm
				{
					if(cb != null)
					{
						eval("var response = " + o.responseText);
						cb(response);
					}
				},

				success : function(o)  //nm
				{
					/* Response is in the form of...
					   { content: <html content for display>,  status: "success" | "error" | <other> , title : <title for display>, [custom...] }
					   */
					this.sendResponse(o);
				},

				failure : function(o)  //nm
				{
					this.sendResponse(o);
				}
			};

			var formel = DOM.get(form);
			var actionUrl = formel.getAttribute('action');
			Y.util.Connect.setForm(formel);
			Y.util.Connect.asyncRequest('POST', actionUrl, callback, null);
		});
	};

	m.addFormValidation = function(formId,fStruct)
	{
		function checkField(e,f,noServerCB) //nm
		{
			var ff = ge(f.id);
			// note - these strings largely must match those in DataFieldException
			if((f.required) && (ff.value.length == 0))
			{
				BLUE.fieldAlert(ff,"Required field");
				return false;
			}
			else
			if((f.minLength > 0) && (ff.value.length < f.minLength))
			{
				BLUE.fieldAlert(ff,"Entry is too short - must have length of at least " + f.minLength + " characters.");
				return false;
			}
			else
			if((f.maxLength > 0) && (ff.value.length > f.maxLength))
			{
				BLUE.fieldAlert(ff,"Entry is too long - must not have length greater than " + f.maxLength + " characters.");
				return false;
			}
			else
			if((f.regex != undefined) && (!((new RegExp(f.regex)).test(ff.value))))
			{
				if(f.regexErrMsg != undefined)
					BLUE.fieldAlert(ff,f.regexErrMsg);
				else
					BLUE.fieldAlert(ff,"This field is not in the proper format.  Please re-enter.");
				return false;
			}
			else
			if((f.ftype == 'integer') && (!(/^-?[0-9]*$/.test(ff.value))))
			{
				BLUE.fieldAlert(ff,"Entry must be a whole number.");
				return false;
			}
			else
			if((f.minValue != undefined) && (parseInt(ff.value) < parseInt(f.minValue)))
			{
				BLUE.fieldAlert(ff,"Entry must be greater or equal to " + f.minValue + ".");
				return false;
			}
			else
			if((f.maxValue != undefined) && (parseInt(ff.value) > parseInt(f.maxValue)))
			{
				BLUE.fieldAlert(ff,"Entry must be less than or equal to " + f.maxValue + ".");
				return false;
			}

			if(f.vjs != undefined)
			{
				if(!f.vjs(f,ff))
					return false;
			}

			BLUE.clearFieldAlert(ff);

			// this may (will) take some time, so clear the error first
			if((f.vserv != undefined) && (!noServerCB))
				BLUE.requestJSON("services.js?name=" + f.vserv + "&value=" + ff.value, function(res) { if(res.status != "OK") BLUE.fieldAlert(ff,res.statusMsg); });

			return true;
		}

		function focusField(e,f)
		{
			var ff = ge(f.id);
			if(f.userTip != undefined)
				BLUE.fieldInfo(ff,f.userTip);
		}

		// checks form validity.  If passed an event and the form fails, it will stopEvents.
		// returns true if clean, false if invalid
		function formsubmit(sev)
		{
			var clean = true;
			for(var i=1; i < fStruct.length ; i++)
			{
				var f = fStruct[i];
				if(!checkField(null,f,true))
					clean = false;
			}

			if(!clean)
			{
				//BLUE.alert("Form Errors","Please correct the errors shown and resubmit");
				if(sev != null)
					EV.stopEvent(sev);
				return false;
			}

			return true;
		}

		if(fStruct.length > 0)
		{
			EV.onDOMReady(function()
				{
					for(var i=1; i < fStruct.length ; i++)
					{
						var f = fStruct[i];
						if(f.c == 'ferr')
							BLUE.fieldAlert(ge(f.id),f.msg);
						else
						{
							EV.on(f.id, "blur", checkField , f);
							EV.on(f.id, "focus", focusField , f);
						}
					}

					// Ensure the formsubmit is called in case the form is submitted
					EV.on(formId,'submit', formsubmit );

					// save off the formsubmit function for calling from outside (but still bound to variables via closure)
					BLUE.forms[formId] = { subfunc: formsubmit };
				});
		}
	};

	m.fieldAlert = function(field, msg)
	{
		var ferr = ge('ferr' + field.id);
		DOM.addClass(field,"fieldAlert");
		if(ferr != null)
		{
			ferr.style.opacity = 0;
			ferr.innerHTML = '<span class="fieldError">' + msg + '</span>';
			var a = new ANIM(ferr, { opacity: { from: 0, to:1 }});
			a.animate();
		}
	};

	m.fieldInfo = function(field, msg)
	{
		var ferr = ge('ferr' + field.id);
		if(ferr != null)
		{
			ferr.style.opacity = 0;
			ferr.innerHTML = '<span class="fieldInfo">' + msg + '</span>';
			var a = new ANIM(ferr, { opacity: { from: 0, to:1 }});
			a.animate();
		}
	};

	m.clearFieldAlert = function(field, msg)
	{
		DOM.removeClass(field,"fieldAlert");
		var ferr = ge('ferr' + field.id);
		if(ferr != null)
			ferr.innerHTML = "";
	};

	// Check all checkboxes within the specified form with the specified prefix
	m.checkAllBoxes = function(form, fieldPrefix)
	{
		e = document.forms[form].elements;
		for(var i=0;i< e.length;i++)
		{
			if(e[i].name.substring(0,fieldPrefix.length) == fieldPrefix)
				e[i].checked = true;
		}
	};

	m.uncheckAll = function(form, fieldPrefix)
	{
		e = document.forms[form].elements;
		for(var i=0;i< e.length;i++)
		{
			if(e[i].name.substring(0,fieldPrefix.length) == fieldPrefix)
				e[i].checked = false;
		}
	};

	// NOTE: Do we need this any longer???
	m.isWellFormedEmailAddress = function(testAddress)
	{
		var filter  = /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;
		if(filter.test(testAddress))
			return true;
		return false;
	};

	m.val = function(field, valObj)
	{
		if(valObj.required)
		{
			if(field.value == "")
			{
				alert('This field is required and must be assigned a value.');
				field.focus();
			}
		}
	};

	/*  The id must be unique.  The calendar will display and ensure the population of shown or hidden form
		fields named id.day, id.month and id.year and the hidden field id.date which is in format of YYYY-mm-dd */
	m.showFormCal = function(divid,fieldName,config)
	{
		BLUE.run("calendar", function() {
			if(config === undefined)
				config = { };

			config.navigator = true;

			var cal = new Y.widget.Calendar("cal" + divid,divid, config);
			cal.render();

			var pNode = ge(divid).parentNode;

			if(config.showYear)
			{
				var year = document.createElement("select");
				for(var ynum=1900;ynum  < 2015; ynum++)
				{
					var opt = document.createElement("option");
					opt.value = ynum;
					opt.appendChild(document.createTextNode(ynum));
					year.appendChild(opt);
				}
				year.id = divid + ".year";
				year.name = divid + ".year";
				pNode.appendChild(document.createTextNode("Jump to Year: "));
				pNode.appendChild(document.createElement("br"));
				year.onchange = function() { cal.setYear(parseInt(this.value)); cal.render(); }  //nm
				pNode.appendChild(year);
				year.value=((new Date()).getFullYear());
			}

			if((config.nofields == undefined) || (!config.nofields))
			{
				var date = document.createElement("input");
				date.id = divid + ".date";
				date.name = fieldName;
				date.type="hidden";
				pNode.appendChild(date);

				var setDate = function(year,month,day)  //nm
				{
					var dField = year + "-" + BLUE.pad(month) + "-" + BLUE.pad(day);
					ge(divid + ".date").value=dField;
					if(config.showYear)
						ge(divid + ".year").value=year;
				}

				var handleCalSelect = function(type,args,obj)  //nm
				{
					var dates = args[0];
					var date = dates[0];
					var year = date[0], month = date[1], day = date[2];
					setDate(year,month,day);
				}

				cal.selectEvent.subscribe(handleCalSelect,cal);

				if(config.selected)
				{
					var date = cal.getSelectedDates()[0];
					setDate(date.getFullYear(),date.getMonth()+1,date.getDate());
				}
			}

			if(config.selectCallback != undefined)
			{
				cal.selectEvent.subscribe(config.selectCallback,cal);
			}
		});
	};

	// ------ DOM stuff

	// returns the DIV following the specified div.
	m.getNextDiv = function(me)
	{
		var next = DOM.getNextSibling(me);
		while((next != null) && (next.nodeName.toLowerCase() != "div"))
			next = DOM.getNextSibling(me);
		return next;
	};

	// use this to ensure something is hidden - regardless of previous state
	m.hideElement = function(el)
	{
		DOM.setStyle(ge(el),'display','none');
	};

	m.rmElement = function(el)
	{
		var e = ge(el);
		e.parentNode.removeChild(e);
	}

	// Hide the div immediately following the specified div
	m.hideNextDivDisplay = function(me)
	{
		var next = m.getNextDiv(me);
		if(next != null)
			DOM.setStyle(next,'display','none');
	};

	// Open a new browser window with the specified URL and a "clean" window
	m.popup = function(url,target,width,height)
	{
		if (width == null)
			width=600;
		if (height == null)
			height = 700;
		var myRef = window.open(url,target,'left=30,top=20,width='+width+',height='+height+',titlebar=no,location=no,status=no,resizable=yes,scrollbars=yes');
		myRef.focus();
		return myRef;
	};

	// use this to ensure an element is shown - regardless of previous state
	m.showElement = function(elmId)
	{
		DOM.setStyle(elmId,'display','block');
	};

	m.showNextDivDisplay = function(me)
	{
		var next = m.getNextDiv(me);
		if(next != null)
			next.style.display = "block";
	};

	m.toggleDisplay = function(elmId)
	{
		var divElm = ge(elmId);
		if(divElm.style.display == 'none')
			divElm.style.display = "block";
		else
			divElm.style.display = "none";
	};

	m.toggleNextDivDisplay = function(me)
	{
		var next = BLUE.getNextDiv(me);

		if(next != null)
		{
			if(next.style.display == 'none')
				next.style.display = "block";
			else
				next.style.display = "none";
		}

		return false;
	};

	// Returns the pageload instance's next DIV num..  (unique across a page load)
	m.nextDivNum = function() { return BLUE.divNum++; };

	/*
		focus on the first field in the specified form
		If no index is specified for the form, focus on the first form field.
		If the specified index is greater than the number of forms - then focus on the last form on the page.
	*/
	m.focusFirstFormField = function(formIndex)
	{
		var numForms = document.forms.length;
		if (formIndex == null)
			formIndex = 0;
		if (numForms > 0)
		{
			if (formIndex >= numForms)
				formIndex = numForms - 1;
			document.forms[formIndex].elements[0].focus();
		}
	};

	// --- Connection / Communication Stuff

	// Loads a javascript file asynchronously, and calls the passed function when done.
	m.loadjs = function(name,func)
	{
		Y.util.Get.script(name, { onSuccess: func });
	};

	// requests a specified URL and loads the response into the specified element
	m.makeRequest = function(url,elementId,func)
	{
		BLUE.run("connection", function() { requestInternal(url,elementId,func); });
	};

	// May be enhanced later, but currently basically redraws the current screen of the specified module
	// and page with new parameters as specified.
	// Example usage:
	//    BLUE.goAjax('<xox:var name="$page:name"/>','<xox:var name="$module:moduleId"/>',
	//		  { start : o.recordOffset, search: "<xox:var name="$search" format="JSONQuoted"/>" });
	// NOTE: Can't we sniff the page?  Maybe even the module?  (page in url, module id in div.. )
	m.goAjax = function(page, module, parms, screen)
	{
		if(ISUD(module))
			location = page + ".html";

		var qstring = '';
		var parm;
		for(parm in parms)
		{
			if(qstring.length > 0)
				qstring += "&";
			qstring += parm + '=' + parms[parm];
		}

		if(screen != null)
			qstring+="&screen=" + screen;

		BLUE.makeRequest(page + '.html?module=' + module + "&" + qstring+"&ajax=true", 'mi'+module);
	};

	m.logout = function()
	{
		document.location.search = '?logoff=true';
	}

	m.pageLoaded = function()
	{
		enableRichTextInt(document);
	};

	m.requestJSON = function(url,func,errFunc)
	{
		BLUE.run("connection", function() {
			var responseSuccess = function(o)  //nm
			{
				var jsonRaw = o.responseText;
				var jsonEval = eval("(" + jsonRaw + ")");
				if(func != undefined)
					func(jsonEval);
				else
					return jsonEval;
			};

			var responseFailure = function(o) //nm
			{
				if(errFunc == undefined)
					alert('Unable to connect to process request.');
				else
					errFunc(o);
			}

			var callback = { success : responseSuccess, failure : responseFailure};

			var transaction = Y.util.Connect.asyncRequest('GET', url, callback, null);
		});
	};

	m.requestJSONs = function(name,atts,func,errFunc)
	{
		var url = 'services.json?name=' + name;
		for(a in atts)
			url += '&' + a + '=' + atts[a];
		return m.requestJSON(url,func,errFunc);
	}

	// ---------- RichText

	m.enableRichText = function(divElm,xconfig)
	{
		//enableRichTextInt(divElm,xconfig);
		alert('enableRichText() - disabled - why are we using this?');
	};

	m.richenTA = function(taDiv,xconfig)
	{
		richenTAint(taDiv,xconfig);
	};

	// sets the 'base' richtext config to inherit this css
	m.setEditorCSS = function(editorCSS)
	{
		editorConfMap.base.editorCSS = editorCSS;
	};

	// Returns the editor configuration against a specific class
	m.getEditorConf = function(className)
	{
		return editorConfMap[className];
	};

	// Map a textarea classname to a specific editor conf
	m.mapEditorConf = function(className, conf)
	{
		editorConfMap[className] = conf;
	};

	// -----------------------

	// This method listens for keypresses on a specific field.  Once a keypress is detected, it waits 200
	// milliseconds and then calls makeRequest() on the url specified.  If during those 200ms another keypress
	// is detected, the counter starts over.  This makes for a good dynamic "hint" system.
	m.keypressRequest = function(field,url,elementId)
	{
		if(timerRunning)
			clearTimeout(timerId);
		timerRunning = false;

		if(field.value.length > 1)
		{
			timerRunning = true;
			f = function() //nm
			{
				requestInternal(url,elementId);
				timerRunning = false;
			}
			timerId = setTimeout(f,200);
		}
		else
		{
			ge(elementId).innerHTML = "";
			ge(elementId).style.display = "none";
		}
	};

	m.startTimeDisplay = function(td,offsetMinutes,showSecs)
	{
		var offsetMinutes = (offsetMinutes == null) ? -1 : offsetMinutes;

		BLUE.updateTime(td,{offsetMinutes:offsetMinutes,showSecs:showSecs});
		if(showSecs)
			setInterval( "BLUE.updateTime('" + td + "',{offsetMinutes:" + offsetMinutes + ",showSecs:true})" ,1000);
		else
			setInterval( "BLUE.updateTime('" + td + "',{offsetMinutes:" + offsetMinutes + ",showSecs:false})",1000*60);
	};

	m.updateTime = function(divId,conf)
	{
		var t = ge(divId);
		var d = new Date();
		if(conf.offsetMinutes != -1)
			d.setTime(d.getTime() + ((d.getTimezoneOffset() - conf.offsetMinutes) * 60 * 1000));
		if(conf.timeOverride != -1)
			d.setTime(conf.timeOverride);
		var dayMod = "";
		var today = new Date();
		if(today.getDate() != d.getDate())  // if dates don't match
			 {
				  if(today.getTime() > d.getTime())
					   dayMod = " (yesterday)";
				else
					 dayMod = " (tomorrow)";
			  }
		var newTime = "";
		if(conf.showDate)
			newTime += d.getFullYear() + "-" + pad(d.getMonth()+1) + "-" + pad(d.getDate()) + " | ";
		newTime += d.getHours() + ":" + pad(d.getMinutes());
		if(conf.showSecs)
			newTime += ":" + pad(d.getSeconds());
		if(!conf.showDate)
			newTime += dayMod;
		t.innerHTML = newTime;
	 };

	m.pad = function(n)
	{
		return pad(n);
	};

	m.debug = function(a,msg,tagtree)
	{
		var dd = ge('debugdiv');
		dd.innerHTML = msg + "<br/>" + tagtree;
		dd.style.display="block";
	};

	m.undebug = function(a)
	{
		var dd = ge('debugdiv');
		dd.style.display="none";
	};

	/*  Provides a simple way to load a set of YUI libs and execute some code.  Simply call like this */
	/*  BLUE.run("connection,animation,stylesheet", function() { ... } );                                     */
	m.run = function(modules,func)
	{
		var loader = new Y.util.YUILoader({ require: modules.split(","), loadOptional: true, base: "javascript/yui/", onSuccess: func});
		loader.insert();
	};

	m.runOnMouseLeaves = function(el,f)
	{
		BLUE.run("dom", function() {
				var elem = DOM.get(el);
				elem.onmouseout = function(e) {  //nm
					var r = Y.util.Region.getRegion(elem);
					var p = new Y.util.Point(e.pageX,e.pageY);
					if(!r.contains(p)) f();
				};
			});
	};

	// provides a callback mechanism for animating anything.  Pass in the callback function
	// the total time, and the time between calls.  callback will include a value between 0 and 1.
	// final value guarenteed to be 1 exactly.
	m.animator = function(fun,totalms,resms,chainfunc)
	{
	  // we will call the function ever <res> ms, passing in a number from 0 to 1 indicating progress until ms
	  var st = (new Date()).getTime();
	  var f = function()
	  {
		var n = (new Date()).getTime();
		if(n >= (st+totalms-(resms/2)))  // very close, hit, or overshot
		{
		  clearInterval(per);
		  fun(1.0);
		  if(chainfunc != null)
			  chainfunc();
		}
		else
		  fun((n-st)/totalms);
	  };
	  //var per = YAHOO.lang.later(resms,this,f,'',true);
	  var per = setInterval(f,resms);
	};

/*

	To use, call with something like:
		showTable( 'divId', {
			caption : 'Some of my favorite wine varietals',
			columns : [ 'name', 'color', 'origin', 'parents' ],
			columnTitles : [ 'Name', 'Color', 'Origin', 'Parents' ],
			data : [
				[ 'Cabernet Sauvignon', 'red', 'France', 'Cabernet Franc / Sauvignon Blanc' ],
				[ 'Chardonnay', 'white', 'France', 'Pinot / Gouais Blanc (Heunisch)' ],
				[ 'Sangiovese' , 'red' , 'Italy', 'Ciliegiolo / Calabrese Montenuovo' ],
				[ 'Zinfandel', 'red', 'Croatia', 'none' ]
			]
		});
*/

	// ------------ TABLES function --------

	// Displays a table in the standard yokohama format
	m.showTable = function(divId,fi)
	{
		var getval = function(r,c) // get data from row,column specified
			{
				var ro = fi.data[r];
				var o = '';
				if(Y.lang.isArray(ro))   //  for arrays of arrays, get the indexed item,
					o = ro[c];
				else  // otherwise, assume it is an object and grab mapped column
					o = ro[fi.columns[c]];
				if(!ISUD(fi.datacon))  // if use wants to convert the data, let them
					o = fi.datacon(fi.columns[c],o,ro,r,c);
				return o;
			};

		var id = '';
		if(!ISUD(fi.id))
			id = ' id = \"' + fi.id + '\"';
		var res = '<table' + id + '>';
		if(!ISUD(fi.caption))
			res +='<caption>' + fi.caption + '</caption>';

		res +='<thead>';
		for(var c=0;c<fi.columns.length;c++)
			res +='<th class=\"' + fi.columns[c] + '\">' + fi.columnTitles[c] + '</th>';
		res +='</thead>';

		res += '<tbody>';
		for(var r=0;r<fi.data.length;r++)
		{
			var odd = '';
			if(r%2 != 0)
				odd = ' class=\"odd\"';
			res += '<tr' + odd + '>';
			for(var c=0;c<fi.columns.length;c++)
				if(c == 0)
					res += '<th class=\"' + fi.columns[c] + '\">' + getval(r,c) + '</th>';
				else
					res += '<td class=\"' + fi.columns[c] + '\">' + getval(r,c) + '</td>';
			res += '</tr>';
		}
		res += '</tbody>';

		res += '</table>';

		ge(divId).innerHTML = res;
	};

	// Display specified matrix table.  This creates the appropriate columns object, sticks it into
	// the config object, and calls showTable.  All config options from showTable are available here.
	// You may either provide a columns/columnsTitles, a columns/colDefs (gets display from colDefs), or just a
	// colDefs (displays first 5 columns)
	m.showMatrixTable = function(divId,fi,colDefs)
	{
		if(!ISUD(fi.columns))
		{
			if(ISUD(fi.columnTitles))  // provided columns, but not titles, so use colDefs..
			{
				fi.columnTitles = [];
				for(c in fi.columns)
				{
					if(fi.columns[c] == 'id')
						fi.columnTitles[fi.columnTitles.length] = 'ID';
					else
					{
						if(ISUD(colDefs[fi.columns[c]]))
						{
							ge(divId).innerHTML = '<b>Column ' + fi.columns[c] +  ' not found.</b>';
							return;
						}
						else
							fi.columnTitles[fi.columnTitles.length] = colDefs[fi.columns[c]].display;
					}
				}
			}
		}
		else
		{  // didn't provide columns or columnTitles, so MUST have provided colDefs, grab first 5
			fi.columns = ['id'];
			fi.columnTitles = ['ID'];
			var maxCols = 5;
			if(!ISUD(fi.maxCols))
				maxCols = fi.maxCols;
			var i = 1;
			for(c in colDefs)
			{
				if(i < maxCols)
				{
					fi.columns[i] = c;
					fi.columnTitles[i] = colDefs[c].display;
					i++;
				}
			}
		}

		m.showTable(divId,fi);
	};

	// Add a new division (or any element) dynamically.  Configure with the c object via the following:
	//   tag : Element tag name (usually 'div' - but can be overridden with any element, 'form' for example)
	//   content : content of the div (innerHTML)
	//   ib : insert before (to place div after a given element, else it is appended to end of parent specified)
	//   ia : insert after (to place div after a given element, else it is appended to end of parent specified)
	//   parent : parent to place div under.  if not specified, appended to end of body element
	//   atts : attribute object with key/values assigned as attributes
	// Example:
	//  BLUE.ad({id: 'foo', content: 'Hello'})
	//     adds <div id="foo">Hello</div> to end of document.
	//  BLUE.ad({ id: 'bar4', content: 'hello', parent: 'bar', atts: { class: 'bar'}});
	//      Adds <div id="bar4" class="bar">hello</div> INTO the element with id 'bar'
	m.ad = function(c)
	{
		var tag = 'div';
		if(c.tag != null)
			tag = c.tag;
		var div = document.createElement(tag);
		if(c.atts != null)
			for(a in c.atts)
				div.setAttribute(a,c.atts[a]);
		if(c.id != null)
			div.setAttribute('id',c.id);
		if(c.content != null)
			div.innerHTML = c.content;
		var par = document.getElementsByTagName('body')[0];
		if(c.parent != null)
			par = ge(c.parent);
		if(c.ia != null)
			DOM.insertAfter(div,c.ia);
		else
			if(c.ib != null)
				DOM.insertBefore(div,c.ib);
			else
				par.appendChild(div);
		return div;
	};

	// Makes a GET call to any URL expecting JSON in the response.  It expects the call to
	// support the "callback" parameter to call a local function with the results.  But allows
	// users of this function to pass in the function, even anonymous functions instead of
	// requiring a globally accessable function that callbacks usually require.  Also may pass
	// in a callFail for failures - if missing, a generic message will be displayed.
	m.jsonCall = function(url,cb,callFail)
	{
		var myCallFail = callFail || function() { alert('Call to ' + url + ' failed.  Please try again later.'); }

		// first, make a random string...
		var rfname = 'cb' + Math.random();
		rfname = rfname.replace('.','');
		window[rfname] = function(res) {
				// uncomment below to see return results in console (to update TestT, etc.)
				//log("call to " + url + " resulted in " + res.toSource());
				if(cb != null)
					cb(res);
				window[rfname] = null;
			}

		if(url.indexOf("?") < 0) url += '?';
		else url += '&';
		url += 'callback=' + rfname;

		YAHOO.util.Get.script(url,
			{ onFailure: callFail,
			   onTimeout : callFail,
			   onSuccess : function(o) { o.purge(); },
			   timeout : 20000 // 20 seconds
			});
	};

	return m;
}();

EV.onDOMReady(BLUE.pageLoaded);
