﻿/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * File:		SMLayoutManager.js
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *
 * Purpose:	client-side layout manager class
 *			
 * Copyright:	Synaptic Mash 2008 All Rights Reserved
 *
 * Classes:
 *		*
 *		
 * Requirements:
 *		*auto includes dependencies ifndef*
 *		prototype.js, SMProgress.js
 *
 * Developer contact:	Jason Hillier <jason.hillier@synapticmash.com>
 * 
 * Notes:
 * ------
 * javascript client-side layout manager
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
 
 var DEBUG_OPTIONS =
	{
		LM_POSTBACK_ELEMENTS : true,
		LM_POSTBACK_SCRIPTS : false,
		AJAX_POSTBACK : false,
		NOTIFICATION_POPUPS: false,
		NOTIFY_PENDING_POSTBACKS: true
	};
 
 //<summary>
 //AJAX interceptor for LayoutManager
 //</summary>
 var SMAjax_ProgressBar;
 
 var SMAjax = {
	RequestCount: 0,
	RequestCompleted: 0,
	_PendingRequests: [],
	_QueuedRequests: [],
	
	Init: function()
	{	
		if (SMLayoutManager._PageInfo.ForceAJAXQueue)
		{
			SMLayoutManager.Log.Debug("[SMAjax.Init] SM ForceAJAXQueue hack enabled");
			
			document.observe("PMAjax:QueueClear", function()
				{
					if (SMAjax._PendingRequests.length == 0 && SMAjax._QueuedRequests.length > 0)
					{
						SMLayoutManager.Log.Trace("[SMAjax] Continuing SM delayed queue...");
						SMAjax.DequeueNextRequest(); //start running qeueued items that have been waiting for the PM._Ajax
					}
				});
		}
	},
	
	Request: function(query, options)
	{
		if (DEBUG_OPTIONS.AJAX_POSTBACK)
			SMLayoutManager.Log.Debug("[SMAjax] Prepping request...");
		
		//setup options values -------------->
		
		options = Object.extend({ Blocking: false, BlockingHash: null }, options); //default Blocking flag is set to false.
		//global blocking override
		if (SMLayoutManager._PageInfo.ForceAJAXQueue)
			options.Blocking = true;
		
		if (options.Blocking)
			options.BlockingHash = null; //Blocking must be done for all; BlockingHash is for a specific set of non-blocking requests. This forces it to behave according to this definition.
		
		if (options.postJSON)
		{
			options = Object.extend(options, {postBody: Object.toJSON(options.postJSON)});
			options.Type=options.postJSON.Type;
			options.postJSON=null;
		}
		
		options = Object.extend(options, {onSuccess: SMAjax.Response, onSuccessOverridden: options.onSuccess});
		//<---------------------
		
		//build query
		if (options.Type)
		{
			var ajaxQueryString;
			ajaxQueryString = "SMUserControlType=" + options.Type;
			ajaxQueryString += "&PageRequestIdentifier=" + SMLayoutManager._PageInfo.PageRequestIdentifier; //tack the RequestID on ALL UserControl AJAX requests
			
			if (query.indexOf('?')>0)
				query += '&' + ajaxQueryString;
			else
				query += '?' + ajaxQueryString;
		}
		
		//repair URL
		query = query.replace('#', '');
		
		//if requests asks for a progress bar or global request for progress bar is ON
		if ((options.Progress!=null && options.Progress==true) || (SMLayoutManager._PageInfo.ProgressBar!=null && SMLayoutManager._PageInfo.ProgressBar==true))
		{
			if (options.Progress!=null && options.Progress==false)
			{
				//do nothing if control explictly requests no progress bar
			}
			else
			{
				//only show progress bar on requests that aren't notifications. (hack)
				if (query.indexOf('notifications.aspx') < 0)
				{
					SMLayoutManager.StartWaiting();
				}
			}
		}
		
		//tack on request #, used for queing/identifying pending requests
		options = Object.extend(options, { RequestNumber: ++SMAjax.RequestCount });
		
		if (SMAjax._CheckForPendingBlockingRequest(options.BlockingHash) ||
			SMAjax._CheckIfPMQueue() )
		{
			SMAjax._QueuedRequests.push({
				RequestNumber: options.RequestNumber,
				Query: query,
				Options: options
			});
			
			var blockingHashLogInfo = "";
			if (options.BlockingHash != null)
				blockingHashLogInfo = "(BlockingHash='" + options.BlockingHash + "')";
				
			SMLayoutManager.Log.Trace("[SMAjax] Request " + options.RequestNumber + " is queued for transmission. " + blockingHashLogInfo);
			
			//
			return null;
		}
		else
		{
			return SMAjax.FireRequest(query, options);
		}
	},
	
	DequeueNextRequest: function(Hash)
	{
		for (var i=0; i<SMAjax._QueuedRequests.length; i++)
		{
			var request = SMAjax._QueuedRequests[i];
			
			if (request.Options.BlockingHash == Hash)
			{
				SMAjax._QueuedRequests.splice(i, 1);
				
				SMAjax.FireRequest(request.Query, request.Options);
				return;
			}
		}
	},
	
	FireRequest: function(query, options)
	{
		try
		{
			document.fire("SMAjax:PreTransmit");
			
			if (DEBUG_OPTIONS.AJAX_POSTBACK)
				SMLayoutManager.Log.Debug("[SMAjax] Request " + options.RequestNumber + " ready, transmittimg...");
			
			SMAjax._PendingRequests.push({
				RequestNumber: options.RequestNumber,
				Request: new Ajax.Request(query, options),
				Blocking: options.Blocking,
				BlockingHash: options.BlockingHash
			});
			
			return (SMAjax._PendingRequests[SMAjax._PendingRequests.length-1].Request);
		}
		catch (ex)
		{
			SMLayoutManager.Log.Error("[SMAjax] Error initiating request " + options.RequestNumber + ": " + ex);
			return null;
		}
	},
	
	Response: function(response)
	{
		var options = response.request.options;
		var requestNumber = response.request.options.RequestNumber;
		
		SMAjax._RemovePendingRequest(requestNumber);
		
		if (DEBUG_OPTIONS.AJAX_POSTBACK)
			SMLayoutManager.Log.Debug("[SMAjax] Received response for " + requestNumber + ".");
			
		if (SMLayoutManager._PageInfo.ForceAJAXQueue && SMAjax._PendingRequests == 0 && SMAjax._QueuedRequests == 0)
		{
			SMLayoutManager.Log.Debug("[SMAjax.Response] fire queue clear event");
			document.fire("SMAjax:QueueClear");
		}
		
		var disableCallBack = false;
		if (response.request.url.indexOf("notifications.aspx") >= 0)
			disableCallBack = true;
		
		var eventResponse = null;
		
		if (response.transport.responseText.indexOf("SMLayoutManager.Header")>0)
		{
			SMAjax.Cache.clear(); //clear inter-AJAX cache objects
			eventResponse = SMLayoutManager.Process(response.transport.responseText, disableCallBack);
		}
		else
		{
			//TODO: better detection of session timeout and handling.
			if (response.transport.responseText.indexOf('LoginTable')>0)
			{
				window.location.reload(true);
				return;
			}
		}
		
		if (response.request.options.onSuccessOverridden)
			response.request.options.onSuccessOverridden(response);
			
		//SMLayoutManager.Log.Debug("[SMLayoutManager] Deepincode - " + eventResponse);
			
		if (response.request.options.onSuccessEventResponse)
			response.request.options.onSuccessEventResponse(eventResponse);
		
		
		//if requests asks for a progress bar or global request for progress bar is ON
		if ((options.Progress!=null && options.Progress==true) || (SMLayoutManager._PageInfo.ProgressBar!=null && SMLayoutManager._PageInfo.ProgressBar==true))
		{
			if (options.Progress!=null && options.Progress==false)
			{
				//do nothing if control explictly requests no progress bar
			}
			else
			{
				//only show progress bar on requests that aren't notifications. (hack)
				if (response.request.url.indexOf('notifications.aspx') < 0)
				{
					SMLayoutManager.StopWaiting();
				}
			}
		}
	},
	
	//inter-request cache. some controls need vars cleared between postbacks.
	Cache:
	{
		Items: [],
		getItem: function(IndexName, DefaultValue)
		{
			for(var i=0; i<this.Items.length; i++)
			{
				if (this.Items[i].name == IndexName)
				{
					return this.Items[i].value;
				}
			}
			
			if (DefaultValue!=null)
			{
				this.setItem(IndexName, DefaultValue);
				return DefaultValue;
			}
			else
			{
				return null;
			}
		},
		setItem: function(IndexName, Value)
		{
			for(var i=0; i<this.Items.length; i++)
			{
				if (this.Items[i].name == IndexName)
				{
					this.Items[i].value = Value;
					return;
				}
			}
			
			this.Items.push({name: IndexName, value: Value});
		},
		clear: function()
		{
			this.Items.clear();
		}
	},
	
	_RemovePendingRequest: function(RequestNumber)
	{
		for(var i=0;i<SMAjax._PendingRequests.length; i++)
		{
			var request = SMAjax._PendingRequests[i];
			if (request.RequestNumber == RequestNumber)
			{
				SMAjax._PendingRequests.splice(i,1);
				
				if (DEBUG_OPTIONS.AJAX_POSTBACK)
					SMLayoutManager.Log.Debug("[SMAjax] " + SMAjax._PendingRequests.length + " requests pending, " + SMAjax._QueuedRequests.length + " in queue.");
				
				SMAjax.DequeueNextRequest(request.BlockingHash);
				
				return;
			}
		}
	},
	
	_CheckForPendingBlockingRequest: function(Hash)
	{
		//see if any pending requests require blocking
		for(var i=0;i<SMAjax._PendingRequests.length; i++)
		{
			if (Hash != null && SMAjax._PendingRequests[i].BlockingHash == Hash)
			{
				return true;
			}
			if (SMAjax._PendingRequests[i].Blocking)
			{
				return true;
			}
		}
		
		return false;
	},
	
	_CheckIfPMQueue: function()
	{
		return (SMLayoutManager._PageInfo.ForceAJAXQueue && PM._Ajax != null && (PM._Ajax.PendingRequestCount() > 0 || PM._Ajax._QueuedRequests.length > 0));
	}
 };
 
 //<summary>
 //client-side javascript infrastructure for the LayoutManager
 //</summary>
 var SMLayoutManager = {
	_PageInfo: {},
	_Overlay: null,
	PopupRef: null,
	_zTop: 9999,
	_Progress: null,
	_DisableFurtherAJAX: false,
	_Controls: [],
	_WaitCounter: 0,
	
	 //<summary>
	 //client-side logging functions. TODO: add X-Browser support
	 //</summary>
	Log:
	{
		_CheckFirebug: function()
		{
			if (window.console && window.console.firebug)
				return true;
			
			if ((isIE || isWebKit) && window.console && window.console.dir)
				return true;
		},
		
		Error: function()
		{
			if (SMLayoutManager.Log._CheckFirebug())
				console.error.apply(console, arguments);
		},
		
		Warning: function()
		{
			if (SMLayoutManager.Log._CheckFirebug())
				console.warn.apply(console, arguments);
		},
		
		Info: function()
		{
			if (SMLayoutManager.Log._CheckFirebug())
				console.info.apply(console, arguments);
		},
		
		Trace: function()
		{
			if (SMLayoutManager.Log._CheckFirebug())
				console.info.apply(console, arguments);
		},
		
		Debug: function()
		{
			if (SMLayoutManager.Log._CheckFirebug())
				console.debug.apply(console, arguments);
		}
	},
	
	//THIS FUNCTION GETS CALLED WHEN THE PAGE IS DONE LOADING
	Init: function(PageInfo)
	{
		SMLayoutManager._PageInfo = PageInfo;
		
		if (!SMLayoutManager._PageInfo.IsPopup && SMLayoutManager._PageInfo.Notifications.PollingTime>0)
		{
			SMLayoutManager.NotificationPoll();
		}
		
		if (SMLayoutManager._PageInfo.Notifications.MessagePositioning + "" != "")
		{
			SMMessageBox._DEFAULT_POS = SMLayoutManager._PageInfo.Notifications.MessagePositioning;
		}
		
		if (SMLayoutManager._PageInfo.Notifications.OffsetX>0)
			SMMessageBox._OFFSET_X = SMLayoutManager._PageInfo.Notifications.OffsetX;
		if (SMLayoutManager._PageInfo.Notifications.OffsetY>0)
			SMMessageBox._OFFSET_Y = SMLayoutManager._PageInfo.Notifications.OffsetY;
		
		
		Event.observe(window, 'beforeunload', this.OnUnload.bindAsEventListener());
		
		//hook up some event handler junk
		SMAjax.Init();
	},
	
	OnUnload: function(e)
	{
		if (DEBUG_OPTIONS.NOTIFY_PENDING_POSTBACKS)
		{
			//notify the user if there are postbacks pending (due to a blocking request)
			if (SMAjax._QueuedRequests.length > 0)
			{
				if (!confirm("There are currently " + SMAjax._QueuedRequests.length + " requests pending, closing this screen without allowing them to finish may cause you to lose work. Continue anyway?"))
				{
					Event.stop(e);
					return false;
				}
			}
		}
		
		var requestCookie = Cookie.get("SMActiveRequest_" + SMLayoutManager._PageInfo.PageRequestIdentifier);
		
		if (requestCookie != null)
		{
			requestCookie = requestCookie.evalJSON();
			
			requestCookie.IsActive = false;
			
			Cookie.set("SMActiveRequest_" + SMLayoutManager._PageInfo.PageRequestIdentifier, Object.toJSON(requestCookie));
		}
	},
	
	GetDebugID: function(ControlId)
	{
		if (ControlId.indexOf('controlid_') >= 0)
		{
			var domElement = $(ControlId);
			if (domElement != null)
			{
				var debugId = domElement.readAttribute('debug_id');
				if (debugId != null)
					return debugId;
			}
			
			//base64 decode what we have and return it
			ControlId = ControlId.replace("controlid_", "");
			return Base64Decode_HtmlSafe(ControlId);
		}
		else
		{
			return ControlId;
		}
	},
	
	RegisterControl: function(control)
	{
		return SMLayoutManager.AddControl(control);
	},
	
	AddControl: function(control)
	{
		if (control == null || control.Name == null)
		{
			SMLayoutManager.Log.Error("Invalid control reference '%o'!", control);
			return;
		}
		
		var ControlIndex = SMLayoutManager.FindControlIndex(control.Name);
		
		if (ControlIndex > -1)
		{
			//update
			SMLayoutManager._Controls[ControlIndex] = control;
			
			SMLayoutManager.Log.Trace("Updated control reference: ", control);
		}
		else
		{
			//insert
			this._Controls.push(control);
			
			SMLayoutManager.Log.Trace("Inserted control reference: ", control); 
		}
	},
	
	GetControl: function(id)
	{
		var ControlIndex = SMLayoutManager.FindControlIndex(id);
		if (ControlIndex > -1)
		{
			return SMLayoutManager._Controls[ControlIndex];
		}
		
		SMLayoutManager.Log.Error("Unable to find control '" + id + "'!");
		
		return null;
	},
	
	FindControlIndex: function(id)
	{
		for(var i=0; i<this._Controls.length; i++)
		{
			if (this._Controls[i].Name == id)
				return i;
		}
		
		return -1;
	},
	
	FireAjaxEvent: function(ControlID, ControlType, EventParameters, Options)
	{
		var debugID = SMLayoutManager.GetDebugID(ControlID);
		//SMLayoutManager.Log.Debug(debugID);
		var ajax_JSON={DebugID: debugID, Name: ControlID, Type: ControlType, FireEvent: EventParameters };
		
		//fire an event when AJAX processing is starting
		document.fire("SMAjax:CallBackStarting", ajax_JSON);
		
		var requestData = Object.extend({method:'post', postJSON: ajax_JSON }, Options);
		return SMAjax.Request(window.location + "", requestData);
	},
	
	FireAjaxEventType: function(ControlID, ControlType, EventType, EventParameters, Options)
	{
		return SMLayoutManager.FireAjaxEvent(ControlID, ControlType, Object.extend({ EventType: EventType }, EventParameters), Options);
	},
	
	FocusFirst: function(params)
	{
		if (params==null)
		{
			focusFirst();
		}
		else
		{
			if (params.async)
			{
				setTimeout("focusFirst();", 1000);
			}
		}
	},
	
	NotificationRequest: function()
	{
		var ajax_JSON={};
		SMAjax.Request("/notifications.aspx", {method:'post', postJSON: ajax_JSON, onComplete: SMLayoutManager.NotificationPoll });
	},
	
	NotificationPoll: function()
	{
		setTimeout("SMLayoutManager.NotificationRequest();", SMLayoutManager._PageInfo.Notifications.PollingTime*1000);
	},
	
	ShowMessage: function(messageText)
	{
		alert(messageText);
	},
	
	_RedirectAfterDOM: function(e, url, redirectParent)
	{
		SMLayoutManager.Redirect(url, redirectParent);
	},
	
	Redirect: function(url, redirectParent, afterDOMLoad)
	{
		if (afterDOMLoad)
		{
			SMLayoutManager.Log.Trace("Redirecting (afterDOMLoad)...");
			document.observe('dom:loaded', SMLayoutManager.Redirect.bind(this, url, redirectParent));
			return;
		}
		
		SMLayoutManager.Log.Trace("Redirecting...");
		
		SMLayoutManager._DisableFurtherAJAX = true;
		
		if (this._PageInfo.IsPopup && redirectParent)
		{
			window.parent.location=url;
		}
		else
		{
			if (this._PageInfo.IsPopup && url.indexOf("LightWindowMaster")<0)
			{
				url=AddQueryParameter(url, "LightWindowMaster=true");
			}
			window.location=url;
		}
	},
	
	GoBack: function(RefreshParent, DefaultPage)
	{
		if (this._PageInfo.IsPopup)
		{
			SMLayoutManager.Log.Debug("[GoBack] Referrer: " + this._PageInfo.Referrer);
			
			if (RefreshParent)
			{
				var refresh = window.parent.location;
				window.parent.location=refresh;
				return;
			}
			else
			{
				if (this._PageInfo.Referrer.indexOf("LightWindowMaster")<0)
				{
					//window.parent.myLightWindow.deactivate();
					window.parent.SMLightWindow.Hide();
					return;
				}
			}
		}
		else
		{
			SMLayoutManager.Log.Debug("[GoBack] Not a popup.");
		}
		
		if (this._PageInfo.Referrer != null)
		{
			window.location = this._PageInfo.Referrer;
		}
		else
		{
			if (DefaultPage == null) DefaultPage = "/";
			window.location = DefaultPage;
		}
	},
	
	LightWindowResize: function(x,y)
	{
		if (this._PageInfo.IsPopup)
		{
			/*
			window.parent.myLightWindow.element.width=x;
			window.parent.myLightWindow.element.height=y;
			window.parent.myLightWindow.effectOnly=true;
			window.parent.myLightWindow._defaultTransitionHandler(null);
			*/
			SMLightWindow.Resize(x,y);
		}
	},
	
	Refresh: function(rParent, showProgressBar)
	{
		if (showProgressBar != null && showProgressBar)
		{
			this._Progress=new SMProgress();
			this._Progress.show();
			
			//hack fix
			setTimeout("SMLayoutManager.Refresh(" + rParent + ", false);", 200);
			return;
		}
		if (this._PageInfo.IsPopup && rParent)
		{
			var refresh = window.parent.location;
			window.parent.location=refresh;
		}
		else
		{
			var refresh = window.location;
			window.location=refresh;
		}
	},
	
	EnableLocalization: function (Page)
	{
		SMLayoutManager._Page=Page;
	},
	ShowLocalization: function(tables)
	{
		SMLightWindow.Show({href:"/localization_popup.aspx?Tables=" + escape(Object.toJSON(tables))});
		/*
		var context=SMLayoutManager._PageInfo.PageHash;
		this.PopupRef=SMLayoutManager.ShowPopup("LocalizationPopup", "/localization_popup.aspx?context=" + context);
		
		SMLayoutManager.ShowOverlay(0.01);
		
		this._Progress=new SMProgress();
		this._Progress.show();
		*/
	},
	
	EditHelp: function()
	{
		var context=SMLayoutManager._PageInfo.HelpHash;
		this.PopupRef=SMLayoutManager.ShowPopup("EditPopup", "/help_popup.aspx?context=" + context + "&value=Help-Info");
		
		SMLayoutManager.ShowOverlay(0.01);
		
		this._Progress=new SMProgress();
		this._Progress.show();
	},
	
	ShowHelp: function(context, value)
	{
		if (!context) context=SMLayoutManager._PageInfo.HelpHash;
		if (!value) value="Help-Info";
		
		this.PopupRef=SMLayoutManager.ShowPopup("ShowHelp", "/showhelp_popup.aspx?context=" + context + "&value=" + value);
		
		SMLayoutManager.ShowOverlay(0.2);
		
		this._Progress=new SMProgress();
		this._Progress.show();
	},
	
	
	//todo: encapsulate and refactor these...
	ShowOverlay: function(opacity)
	{
		//fade out background
		if (!this._Overlay)
		{
			this._Overlay=document.createElement("DIV");
			document.body.appendChild(this._Overlay);
		}
		
		var width=document.body.scrollWidth;
		var height=document.body.clientHeight+document.body.scrollHeight;
		
		if (!isIE)
		{
		    width=window.innerWidth;
		    height=window.innerHeight;
		}
		
		$(this._Overlay).setStyle({position:'absolute', backgroundColor:'#000000',zIndex:1});
		this._Overlay.style.width=width + "px";
		this._Overlay.style.height=height + "px";
		this._Overlay.style.top=0;
		this._Overlay.style.left=0;
		this._Overlay.style.opacity=0;
		this._Overlay.style.filter="alpha(opacity=0)"; 
		new Effect.Appear(this._Overlay, { duration: 0.2, from: 0.0, to: opacity });
		this._Overlay.style.display='';
	},
	
	HideOverlay: function()
	{
		if (this._Overlay)
		{
			this._Overlay.style.display='none';
		}
	},
	
	ShowPopup: function(PopupName, url)
	{
		var PopupHandle=document.getElementById(PopupName + "_DIV");
		var PopupHandle_IFRAME=null;
		var PopupHandle_HEADER=null;
		
		if (PopupHandle)
		{
			$(PopupHandle).remove();
		}
		
		PopupHandle=document.createElement("DIV");
		PopupHandle.id=PopupName + "_DIV";
		document.body.appendChild(PopupHandle);
		
		this._zTop++;
		$(PopupHandle).setStyle({zIndex: this._zTop});
		
		PopupHandle_IFRAME=document.createElement('IFRAME');
		PopupHandle_IFRAME.id=PopupName + "_IFRAME";
		PopupHandle_IFRAME.scrolling='no';
		
		PopupHandle_HEADER=document.createElement("DIV");
		PopupHandle_HEADER.innerHTML="&nbsp;";
		PopupHandle.appendChild(PopupHandle_HEADER);
		$(PopupHandle_HEADER).setStyle({height: '25px'});
		PopupHandle.appendChild(PopupHandle_IFRAME);
		
		new Draggable(PopupName + "_DIV", {MouseOutContainer: PopupHandle });
		
		Object.extend(PopupHandle, {IFRAME: PopupHandle_IFRAME, HEADER: PopupHandle_HEADER, ReplaceElement:null});

		PopupHandle_IFRAME=$(PopupHandle).IFRAME;
		PopupHandle_HEADER=$(PopupHandle).HEADER;
		
		//something is wrong with css add so we will do it manually for now --
		//$(SMLayoutManager._LocalizationPopupDIV).addClassName="Localization_Popup_DragHandle";
		$(PopupHandle).setStyle({backgroundColor: '#5E6E7D', cursor:'move', borderTop: 'solid 1px #000000'});
		//--
		
		$(PopupHandle).setStyle({position:'absolute', left:'100px', top:'60px', visibility: 'hidden'});
		$(PopupHandle_IFRAME).setStyle({display:'block', border:'0px', backgroundColor:'transparent', zIndex:'9999',height:'400px',width:'500px'});
		PopupHandle_IFRAME.frameborder=0;
		PopupHandle_IFRAME.scrolling='no';
		PopupHandle_IFRAME.src=url + "&popupName=" + PopupName;
		
		return PopupHandle;
	},
	
	PopupInit: function()
	{
		var view=document.getElementById("tablev"); //TODO: refactor this
		
		$(SMLayoutManager.PopupRef).setStyle({visibility:''});
		$(SMLayoutManager.PopupRef).IFRAME.setStyle({height: view.offsetHeight + "px",width: view.offsetWidth + "px"});
		
		try
		{
			window.parent.SMLayoutManager._Progress.hide();
		}
		catch (ex)
		{
		}
	},
	
	PopupClose: function(doRefresh)
	{
		SMLayoutManager.PopupRef.style.visibility='hidden';
		$(SMLayoutManager.PopupRef).IFRAME.src='';
		
		if (doRefresh==true)
		{
			window.parent.location.reload(true);
		}
		else
		{
			try
			{
				window.parent.SMLayoutManager.HideOverlay();
			}
			catch (ex)
			{
			}
			
			if (doRefresh=="ReplaceElement")
			{
				//get contents of tinyMCE
				if ($(SMLayoutManager.PopupRef).ReplaceElement && tinyMCE)
				{
					$(SMLayoutManager.PopupRef).ReplaceElement.innerHTML=tinyMCE.get('HelpInfo').getContent();
					
					//clear object extensions so they will be re-initialized
					//TODO: this needs a more consistent/reliable method
					$($(SMLayoutManager.PopupRef).ReplaceElement).EditButton=null;
				}
			}
		}
	},
	
	Process: function(JSONData, disableCallBack)
	{
		var eventResponse = null;
		
		//fire an event when AJAX processing is complete
		document.fire("SMAjax:PreProcessCallBack", JSONData);
		
		if (JSONData.isJSON())
		{
			JSONData=JSONData.evalJSON();
			
			for (var vIndex=0; vIndex<JSONData.length; vIndex++)
			{
				if (JSONData[vIndex].Name=="SMLayoutManager.Header")
				{
					//reserved for future use
				}
				else if (JSONData[vIndex].Name=="SMLayoutManager.EventResponse")
				{
					eventResponse = JSONData[vIndex].Value;
					
					if (eventResponse.isJSON())
						eventResponse = eventResponse.evalJSON();
				}
				else if (JSONData[vIndex].Name=="SMLayoutManager.Script")
				{
					var commands = JSONData[vIndex].Value.evalJSON();
					
					for(var i=0; i<commands.length; i++)
					{
						try
						{
							if (DEBUG_OPTIONS.LM_POSTBACK_SCRIPTS)
								SMLayoutManager.Log.Debug("[LayoutManager] Executing script %s of %s: " + commands[i], i+1, commands.length);
							
							eval(commands[i]);
						}
						catch (ex)
						{
							SMLayoutManager.Log.Error("[LayoutManager] Error [" + ex + "] while executing: " + commands[i]);
						}
					}
					
					//this is a flag that scripts can set to short-circuit AJAX processing
					if (SMLayoutManager._DisableFurtherAJAX)
					{
						return;
					}
				}
				else
				{
					var elementID=JSONData[vIndex].Name;
					var element=document.getElementById(elementID);
					if (element)
					{
						var ReplaceData=JSONData[vIndex].Value;
						if (ReplaceData.indexOf("<div")>=0 && (ReplaceData.indexOf("id='" + elementID + "'")>0 || ReplaceData.indexOf("id=\"" + elementID + "\"")>0))
						{
							if (DEBUG_OPTIONS.LM_POSTBACK_ELEMENTS)
								SMLayoutManager.Log.Debug("[LayoutManager] Replacing element '%s' to %o", SMLayoutManager.GetDebugID(elementID), {ReplacementContent: ReplaceData});
							
							try
							{
								SetOuterHTML(elementID, ReplaceData);
							} catch(e)
							{
								SMLayoutManager.Log.Error("[LayoutManager] AJAX outer element '" + SMLayoutManager.GetDebugID(elementID) + "' replacement error occurred. ERROR:" + e);
							}
						}
						else
						{
							if (DEBUG_OPTIONS.LM_POSTBACK_ELEMENTS)
								SMLayoutManager.Log.Debug("[LayoutManager] Replacing element '%s' content to %o", SMLayoutManager.GetDebugID(elementID), {ReplacementContent: ReplaceData});
							
							try
							{
								element.innerHTML=ReplaceData;
							} catch(e)
							{
								SMLayoutManager.Log.Error("[LayoutManager] AJAX inner element '" + SMLayoutManager.GetDebugID(elementID) + "' replacement error occurred. ERROR:" + e);
							}
						}
					}
					else
					{
						SMLayoutManager.Log.Warning("[LayoutManager] Could not find element %s!", SMLayoutManager.GetDebugID(elementID));
					}
				}
			}
			
			if (!disableCallBack)
			{
				//fire an event when AJAX processing is complete
				document.fire("SMAjax:CallBack", JSONData);
				SMAjax.RequestCompleted++;
			}
		}
		
		return eventResponse;
	},
	
	ScaleY: function(dimensions)
	{
		return this.ScaleXY(dimensions)[1];
	},
	ScaleX: function(dimensions)
	{
		return this.ScaleXY(dimensions)[0];
	},
	
	ScaleXY: function(dimensions)
	{
		var ratio = dimensions[0]/dimensions[1];
		if (dimensions[0]>this.ScreenX())
		{
			var cut = dimensions[0] - this.ScreenX();
			dimensions[0] = dimensions[0] - cut;
			dimensions[1] = dimensions[0]/ratio;
		}
		if (dimensions[1]>this.ScreenY())
		{
			var cut = dimensions[1] - this.ScreenY();
			dimensions[1] = dimensions[1] - cut;
			dimensions[0] = dimensions[1]*ratio;
		}
		return dimensions;
	},
	
	ScreenX: function()
	{
		var width=0;
		if (window.document.documentElement.clientWidth>width)
			width=window.document.documentElement.clientWidth;
		if (window.document.body.clientWidth>width)
			width=window.document.body.clientWidth;
		if (window.document.innerWidth>width)
			width=window.document.innerWidth;
		return width;
	},
	ScreenY: function()
	{
		if (window.document.innerHeight>0)
			return window.document.innerHeight;
		if (window.document.documentElement.clientHeight>0)
			return window.document.documentElement.clientHeight;
		if (window.document.body.clientHeight>0)
			return window.document.body.clientHeight;
		return height;
	},
	_ScrollInProgress: false,
	ScrollTo: function(x,y)
	{
		window.scrollTo(x,y);
	},
	
	//currently only implemented for window vertical scroll and DIV horizontal scroll.
	ScrollIntoView: function(element, parentScrollingElement)
	{
		SMLayoutManager._ScrollInProgress = true;
		
		//scroll element into screen
		var pos = element.cumulativeOffset();
		if (pos[0] + 100 > SMLayoutManager.ScreenX())
			pos[0] = pos[0] + (element.getWidth() + 100);
		if (pos[1] + 100 > SMLayoutManager.ScreenY())
			pos[1] = pos[1] + (element.getHeight() + 100);
		
		var x = pos[0] - SMLayoutManager.ScreenX();
		var y = pos[1] - SMLayoutManager.ScreenY();
		
		SMLayoutManager.Log.Debug("scroll to: [" + x + ", " + y + "]");
		
		SMLayoutManager.ScrollTo(x, y);
		
		if (parentScrollingElement != null && parentScrollingElement != window)
		{
			//scroll element into view of scrollable area
			var offset = ((element.offsetLeft + element.getWidth()) - parentScrollingElement.getWidth());
			if (offset < 0)
				offset = 0;
			parentScrollingElement.scrollLeft = offset;
		}
		
		setTimeout("SMLayoutManager._ScrollInProgress = false", 10);
	},
	
	StartWaiting: function()
	{
		if (SMLayoutManager._WaitCounter >= 0)
		{
			SMLayoutManager.Log.Debug("[SMLayoutManager] StartWaiting");
			
			if ($$('.GlobalWaitAnchor').length > 0)
			{
				$$('.GlobalWaitAnchor')[0].startWaiting();
			}
			else
			{
				if (!SMAjax_ProgressBar) SMAjax_ProgressBar=new SMProgress();
				SMAjax_ProgressBar.show();
			}
		}
		
		SMLayoutManager._WaitCounter++;
	},
	
	StopWaiting: function()
	{
		SMLayoutManager._WaitCounter--;
		
		if (SMLayoutManager._WaitCounter < 0)
			SMLayoutManager._WaitCounter = 0;
		
		if (SMLayoutManager._WaitCounter == 0)
		{
			SMLayoutManager.Log.Debug("[SMLayoutManager] StopWaiting");
			
			if ($$('.GlobalWaitAnchor').length > 0)
			{
				$$('.GlobalWaitAnchor')[0].stopWaiting();
			}
			else
			{
				if (!SMAjax_ProgressBar) SMAjax_ProgressBar=new SMProgress();
				SMAjax_ProgressBar.hide();
			}
		}
	}
 }
 
 /**
 *	Utility class for accessing document cookies.
 */
var SMCookies={
	options:null,
	initialize:function(options){
		this.options=Object.extend({
			expires: 7200, // 2 hours
			path: '/',
			domain: '',
			prefix: 'SM__'
		},options||{});
		
		if (this.options.expires) {
			var date = new Date();
			date = new Date(date.getTime() + (this.options.expires * 1000));
			this.options.expires = '; expires=' + date.toGMTString();
		}
		if (this.options.path) {
			this.options.path = '; path=' + escape(this.options.path);
		}
		if (this.options.domain) {
			this.options.domain = '; domain=' + escape(this.options.domain);
		}
	},
	set:function(name,value){
		switch(typeof(value)) {
		  case 'function':
		  case 'boolean': 
		  case 'string': 
		  case 'number':
			value = value.toString();
			break;
		  case 'undefined':
		  case 'function':
		  case 'unknown': 
			return false;
		}
		var cookie_str = this.options.prefix + name + "=" + escape(Object.toJSON(value));
		try {
			document.cookie = cookie_str + this.options.expires + this.options.path + this.options.domain;
			return true;
		}
		catch(e){ return false; }
	},
	get:function(name) {
		var cookies = document.cookie.match(this.options.prefix + name + '=(.*?)(;|$)');
		if (cookies) {
			return (unescape(cookies[1])).evalJSON();
		}
		else {
			return null;
		}
	},
	remove:function(name) {
		try {
			var date = new Date();
			date.setTime(date.getTime() - (3600 * 1000));
			var expires = '; expires=' + date.toGMTString();
			document.cookie = this.options.prefix + name + "=" + expires + this.options.path + this.options.domain;
			return true;
		}
		catch(e){ return false; }
	}
};
SMCookies.initialize();

 //*************/
 //Basic User Controls
 //TODO: this will all end up in different places when the dependency manager is done
 //**************/
 
 var SMTemplateControl = Class.create({
	Name: null,
	
	initialize: function(id, options)
	{
		this.Name = id;
	},
	
	ExecuteTemplate: function(templateText, instructionScript, templateObjects, callBackBinding)
	{
		SMLayoutManager.FireAjaxEventType(this.Name, 'SMTemplateControl', 'OnExecute', { TemplateText: templateText, InstructionScript: instructionScript, TemplateObjects: templateObjects }, { onSuccessEventResponse: callBackBinding });
	},
	
	CreateTemplateObject: function(objectType, name, value)
	{
		return {ObjectType: objectType, Name: name, Value: value}
	}
 });
 
 /*
 var TemplateObject = Class.create({
	_ObjectType: null,
	_Name: null,
	_Value: null,
	
	initialize: function(objectType, name, value)
	{
		this._ObjectType = objectType;
		this._Name = name;
		this._Value = value;
	},
	
	Types: ['CreateObject', 'ConstantValue']
 });
 */
 
 var SMRating = Class.create({
	_Name: null,
	_RatingControl: null,
	_Options: {},
	initialize: function(id, options)
	{
		this._Name = id;
		this._RatingControl = $(id);
		this._Options = options;
		
		this.SetRating(options.Value);
		
		if (!this._Options.ReadOnly)
			this.BindEvents();
	},
	
	SetRating: function(val,showSelected)
	{
		for (var i=1; i<=this._Options.MaxRating; i++)
		{
			var element = $(this._RatingControl.childNodes[i-1]);
			
			if (i<val)
			{
				element.removeClassName("Selected");
				element.removeClassName("None");
				if (i>val-1)
				{
					element.addClassName("Half");
				}
				else
				{
					element.addClassName("Full");
				}
			}
			else
			{
				element.removeClassName("Full");
				element.removeClassName("Half");
				if (i==val)
				{
					element.removeClassName("None");
					if (showSelected)
						element.addClassName("Selected");
					else
						element.addClassName("Full");
				}
				else
				{
					element.removeClassName("Selected");
					element.addClassName("None");
				}
			}
		}
	},
	
	BindEvents: function()
	{
		for (var i=1; i<=this._Options.MaxRating; i++)
		{
			Event.observe(this._RatingControl.childNodes[i-1], "mouseover", this.OnMouseOver.bindAsEventListener(this, i));
			Event.observe(this._RatingControl.childNodes[i-1], "click", this.OnMouseClick.bindAsEventListener(this, i));
		}
		
		Event.observe(this._RatingControl, "mouseout", this.OnMouseLeaveAll.bindAsEventListener(this));
	},
	
	OnMouseOver: function(e, index)
	{
		this.SetRating(index, true);
	},
	
	OnMouseClick: function(e, index)
	{
		this._Options.Value = index;
		
		SMLayoutManager.FireAjaxEventType(this._Name, 'SMRating', 'OnChange', { Value: this._Options.Value });
	},
	
	OnMouseLeaveAll: function(e)
	{
		this.SetRating(this._Options.Value);
	}
 });
 
 //Chat activities namespace
var SMChat = {
	Window: null,
	ChatMenu: null,
	SubMenu: null,
	EffectInt: null,
	MenuVisible: false,
	EnableChatMenuFlag: false,
	
	GetWindow: function( Url )
	{
		var Name = "SMChatWindow";
		var Options = "toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=1,width=550,height=350,left=445,top=275";
		SMChat.Window = open( "", Name, Options );
		if ( !SMChat.Window || SMChat.Window.closed || !SMChat.Window.document.getElementById('ChatManager') ) {
			SMChat.Window = open( Url, Name, Options );
		}
		SMChat.Window.focus();
		return SMChat.Window;
	},
	
	GetChatManager: function(Url)
	{
		var Window = SMChat.GetWindow(Url);
		var ChatManager = Window.document.getElementById('ChatManager');
		if ( ChatManager != undefined ) return ChatManager.self;
		return null;
	},
	
	StartChatWith: function(UserID)
	{
		SMChat.RemoveRequestMarker();
		
		var Manager = SMChat.GetChatManager("/Chat.aspx?client=" + UserID);
		if ( Manager != undefined ) Manager.addTab( UserID, '' );
	},
	
	StartChatRoom: function(RoomID)
	{
		var Manager = SMChat.GetChatManager("/Chat.aspx?room=" + RoomID);
		if ( Manager != undefined ) Manager.addTab( '', RoomID );
	},
	
	Notify: function()
	{
		Sound.play('/media/js/Audio/chat_notify.wav');
		self.focus();
	},
	
	ShowMenu: function(e, menuID)
	{
		var element = SMChat.SubMenu;
		
		if (element.getStyle('visibility')!='visible')
		{
			if (isIE)
			{
				element.setStyle({visibility: "visible"});
			}
			else
			{
				Element.setStyle(element, {visibility: "visible",filter:"alpha(opacity=0)",opacity:0.0});
				new Effect.Appear(element, { duration: 0.2, from: 0.1, to: 0.9 });
			}
		}
		
		SMChat.MenuVisible = true;
	},
	
	HideMenu: function(e, menuID)
	{
		SMChat.MenuVisible = false;
		setTimeout("SMChat.TriggerHideMenu('" + menuID + "');", 500);
	},
	
	ClickMenu: function(e, menuID)
	{
		var element = $(menuID).down('.HasChatRequest');
		
		if (element != null)
		{
			SMChat.RemoveRequestMarker();
			
			simulateClick(element);
		}
	},
	
	RemoveRequestMarker: function()
	{
		var chatReqElement = SMChat.ChatMenu.down('.HasChatRequest');
		if (chatReqElement != null)
		{
			//TODO: align this with correct chat request
			$(chatReqElement).removeClassName("HasChatRequest");
		}
	},
	
	TriggerHideMenu: function(menuID)
	{
		if (!SMChat.MenuVisible)
		{
			SMChat.SubMenu.setStyle({visibility:'hidden'});
		}
	},
	
	InitMenu: function(menuID)
	{
		SMChat.MenuNotification(menuID);
		if (!SMChat.EnableChatMenuFlag)
		{
			//disable JS styling
			return;
		}
		
		if ($(menuID)==null) return;
		
		if ($(menuID).down('.OnlineFriendItem')==null)
		{
			//there are no items in the list
			SMChat.SubMenu = null;
			return;
		}
		
		SMChat.SubMenu = $(menuID).down('.OnlineFriendsList');
		Element.absolutize(SMChat.SubMenu);
		SMChat.SubMenu.setStyle({width:'auto',height:'auto'});
		
		//TODO: clean up event handlers on AJAX refreshes
		Event.observe($(menuID), "mouseover", SMChat.ShowMenu.bindAsEventListener(this, menuID));
		Event.observe($(menuID), "mouseout", SMChat.HideMenu.bindAsEventListener(this, menuID));
		Event.observe($(menuID), "click", SMChat.ClickMenu.bindAsEventListener(this, menuID));
		
		SMChat.MenuNotification(menuID);
	},
	
	MenuNotification: function(menuID)
	{
		if (SMChat.EffectInt != null) clearInterval(SMChat.EffectInt);
		if ($(menuID)==null)
			return;
		
		SMChat.ChatMenu = $(menuID);
		
		SMChat.EffectInt = setInterval("SMChat.MenuNotificationFlash();", 1200);
	},
	
	MenuNotificationFlash: function()
	{
		if (SMChat.ChatMenu.down('.HasChatRequest') != null)
		{
			if (SMChat.ChatMenu.hasClassName("NotifyActive"))
				SMChat.ChatMenu.removeClassName("NotifyActive");
			else
				SMChat.ChatMenu.addClassName("NotifyActive");
		}
		else
		{
			SMChat.ChatMenu.removeClassName("NotifyActive");
			clearInterval(SMChat.EffectInt); //no reason to refresh
		}
	}
}
 
 function getName(vName)
 {
	return vName.substring(vName.indexOf('_')+1);
 }

var SMNavBar={
	PostBack: function(name, index, options)
	{
		SMLayoutManager.FireAjaxEventType(name, 'SMNavBar', 'OnClick', { ButtonIndex: index}, { Progress: options.OnClick_ProgressBar });
	}
}

var SMPanelWizard={
	_CurrentID: '',
	_zIndex:1,
	_inTransition:false,
	
	Init: function(id, options)
	{
		Object.extend($(id), { Options: options });
	},
	
	MoveNext: function(id, value)
	{
		if (SMPanelWizard._inTransition) return;
		
		SMPanelWizard._prepForTransition(id);
		
		SMPanelWizard.FireCallback(id, { Value: value, Action: "Next", PageIndex: $(id).Options.PageIndex });
	},
	
	MovePrevious: function(id, value)
	{
		if (SMPanelWizard._inTransition) return;
		
		SMPanelWizard._prepForTransition(id);
		
		SMPanelWizard.FireCallback(id, { Value: value, Action: "Previous", PageIndex: $(id).Options.PageIndex });
	},
	
	FireCallback: function(id, parameters)
	{
		SMLayoutManager.FireAjaxEventType(id, 'SMPanelWizard', 'On' + parameters.Action, parameters);
	},
	
	_prepForTransition: function(id)
	{
		SMPanelWizard._inTransition=true;
		
		setTimeout("SMPanelWizard._inTransition=false;", 1000);
	},
	
	Transition: function(id, params)
	{
		if (params.Action=="Cancel")
		{
			//$("SMPanelWizardContent_" + id).remove();
			//$("SMPanelWizardContentOld_" + id).id="SMPanelWizardContent_" + id;
			SMPanelWizard._inTransition=false;
			return;
		}
		else
		{
			var newContent=$("SMPanelWizardContent_" + id).cloneNode(true);
			newContent.setStyle({display:'none'});
			newContent.innerHTML = params.Content;
			$("SMPanelWizardContent_" + id).id="SMPanelWizardContentOld_" + id;
			
			$("SMPanelWizardContentOld_" + id).parentNode.insertBefore(newContent,$("SMPanelWizardContentOld_" + id));
		}
		
		$(id).Options.PageIndex=params.PageIndex;
		
		Position.includeScrollOffsets = true;
		Position.absolutize($("SMPanelWizardContentOld_" + id));//, $("SMPanelWizardContentOld_" + id));
		
		SMPanelWizard._zIndex++;
		$("SMPanelWizardContent_" + id).setStyle({display:'',visibility:'hidden',zIndex:SMPanelWizard._zIndex});
		
		SMPanelWizard._CurrentID = id;
		if (params.Action=="Next")
		{
			Effect.SlideLeftIn($("SMPanelWizardContent_" + id), {duration: .5,afterUpdate:SMPanelWizard._ShowInTrans});
			Effect.SlideLeftOut($("SMPanelWizardContentOld_" + id), {duration: .5,afterFinish:SMPanelWizard._TransitionComplete});
		}
		else
		{
			Effect.SlideRightIn($("SMPanelWizardContent_" + id), {duration: .5,afterUpdate:SMPanelWizard._ShowInTrans});
			Effect.SlideRightOut($("SMPanelWizardContentOld_" + id), {duration: .5,afterFinish:SMPanelWizard._TransitionComplete});
		}
	},
	
	_ShowInTrans: function()
	{
		//hack to remove flashing
		$("SMPanelWizardContent_" + SMPanelWizard._CurrentID).setStyle({visibility:''});
	},
	
	_TransitionComplete: function()
	{
		$("SMPanelWizardContentOld_" + SMPanelWizard._CurrentID).remove();
		//$("SMPanelWizardContent_" + SMPanelWizard._CurrentID).setStyle({display:''});
		
		SMPanelWizard._inTransition=false;
	}
}
 
 //first version hack together. this will use a proper event-based OO model later
var SMAccordion = Class.create({
	_accordion:null,
	_options:{},
	_id:null,
	initialize:function(id){
		this._id=id;
		this._options=Object.extend({
				CssClass: null,
				SelectedPanel: null,
				CallbackOnToggle: false
			}, arguments[1] || { });
		
		this.init(id, this._options.CssClass);
	},
	
	init: function(accordionID, cssName)
	{		
		var ClassNames = {
			toggle: cssName + "_toggle",
			toggleActive: cssName + "_toggle_active",
			content: cssName + "_content"
		};
		
		this._accordion=new accordion(accordionID, { PostBackURL: window.location + "", classNames: ClassNames, OnActivated: this.OnActivated.bind(this) });
		
		//hide accordian panels
		var verticalAccordions = $(accordionID).select('.' + ClassNames.toggle);
		verticalAccordions.each(function(a) {
			if(!a.hasClassName(ClassNames.toggleActive)){
				$(a.next(0)).setStyle({display: 'none'});
			}
		});
	},
	
	OnActivated: function(){
		if(this._options.CallbackOnToggle){
			var titleElement = this._accordion.currentAccordion.previous();
			this.DoCallback('OnActivated', {SelectedTitle: titleElement.innerHTML });
		}
	},
	
	DoCallback:function(EventType, parameters){
	
		SMLayoutManager.FireAjaxEventType(this._id, 'SMAccordion', EventType, parameters);
	
	}

});

var SMMessageBox={
	BoxQueue: [],
	_OFFSET_Y: 25,
	_OFFSET_X: 20,
	_DEFAULT_POS: "top-right",
	_Position: '',
	_ScrollOffset: 0,
	_Container: null,
	_HoverElement: null,
	_PollingID: 0,
	_IsPolling: false,
	
	Show: function(id, params)
	{
		this.AddMessage($(id), params); //move out of any pass-through container and take posession
		
		this.ExitEffectPolling(false);
	},
	
	ExitEffectPolling: function(timerReq)
	{
		if (DEBUG_OPTIONS.NOTIFICATION_POPUPS)
			return;
		
		if (this._IsPolling!=timerReq) return;
		
		var BoxItem = this.BoxQueue[this._PollingID];
		
		//									..hack for some wierd problem
		if (BoxItem.ExitComplete==false && BoxItem.element !=null && BoxItem.element.innerHTML != "")
		{
			if (this.ExitEffect(this._PollingID))
			{
				this._IsPolling=true;
				setTimeout("SMMessageBox.ExitEffectPolling(true);",50);
			}
			
			this._IsPolling=true;
			setTimeout("SMMessageBox.ExitEffectPolling(true);",200);
		}
		else
		{
			if (this._PollingID<this.BoxQueue.length-1)
			{
				this._PollingID++;
				this._IsPolling=true;
				BoxItem = this.BoxQueue[this._PollingID];
				//reset transition info when going to the next item
				BoxItem.init=null;
				BoxItem.ExitEffectIndex=null;
				//
				return this.ExitEffectPolling(true);
			}
			else
			{
				this._IsPolling=false;
				return;
			}
		}
	},
	
	AddMessage: function(element, params)
	{
		if (this._Container==null)
		{
			this._Container = document.createElement("DIV");
			this._Container.id = "SMMessageContainer";
			document.body.appendChild(this._Container);
			this._Container = $(this._Container);
			
			var topPos = this._Container.cumulativeScrollOffset()[1];
			
			this._Position = SMLayoutManager._PageInfo.MessagePositioning;
			if (this._Position==null) this._Position= this._DEFAULT_POS; //default position
			
			this.OnDocumentScroll(); //set vertical alignment
			
			this._Container.setStyle({position:'absolute',left:'0px',zIndex:65535});
			
			Event.observe(window, "scroll", SMMessageBox.OnDocumentScroll.bindAsEventListener(this));
		}
		
		//special FX hack fix wrapper thing... don't know why I even need to do this but hey it works---
		var wrapperFix = document.createElement("DIV");
		wrapperFix.id=element.id;
		element.id="";
		this._Container.appendChild(wrapperFix);
		
		wrapperFix.appendChild(element);
		var messageElement=element;
		element=wrapperFix;
		//---
		
		this.BoxQueue.push({element: element, elementClone: null, params: params, ExitComplete: false, init: null, ExitEffectIndex:null, ActionEffect: null, showEffect:null});
		var BoxID = this.BoxQueue.length-1;
		
		//SHOW MESSAGE
		this.ShowItem(BoxID);
		
		//event binding
		Event.observe(element, "mouseout", SMMessageBox.OnElementOut.bindAsEventListener(this, BoxID));
		Event.observe(element, "mouseover", SMMessageBox.OnElementEnter.bindAsEventListener(this, BoxID));
		Event.observe(element, "click", SMMessageBox.OnElementClick.bindAsEventListener(this, BoxID));
		
		//reposition HORIZONTALLY
		var leftPos = 0;
		if (this._Position.indexOf("center")>=0)
		{
			leftPos = Math.round(SMLayoutManager.ScreenX()/2);
			leftPos -= Math.round(messageElement.getWidth()/2);
		}
		if (this._Position.indexOf("right")>=0)
		{
			leftPos = SMLayoutManager.ScreenX();
			leftPos -= messageElement.getWidth();

			leftPos -= this._OFFSET_X;
		}
		if (this._Position.indexOf("left")>=0)
		{
			leftPos = this._OFFSET_X;
		}

		this._Container.setStyle({left:leftPos + 'px'});
		
		//VERTICAL repositioning is done via scrollbar polling
		//
		return BoxID;
	},
	
	ShowItem: function(BoxID)
	{
		this.ExitEffect_Cancel(BoxID);
		
		var element = this.BoxQueue[BoxID].element;
		
		$(element).setStyle({display:'block',opacity:'1.0',filter:'opacity=1.0',zIndex:9999});
	},
	
	ExitEffect: function(BoxID)
	{
		var BoxItem = this.BoxQueue[BoxID];
		var element = BoxItem.element;
		
		if (element==null)
		{
			alert('[debug] no element');
			BoxItem.ExitComplete=true;
			return true;
		}
		if (element==this._HoverElement) return false;
		
		if (BoxItem.ActionEffect!=null) return false; //don't move to next effect until this one is done
		
		//process chosen effect
		if (BoxItem.ExitEffectIndex==null)
		{
			BoxItem.ExitEffectIndex = 0;
		}
		
		var ExitEffect=BoxItem.params.ExitEffects[BoxItem.ExitEffectIndex];
		
		//ONLY do effect if DURATION has passed >>
		if (BoxItem.init==null) BoxItem.init = new Date();
		
		var tDiff = (new Date()).getTime() - BoxItem.init.getTime();
		if (tDiff<ExitEffect.Delay*1000)
			return false;
		//<<
		
		element = this.ExitEffect_Prep(BoxID);
		
		if (ExitEffect.Type=="fade")
		{
			BoxItem.ActionEffect = new Effect.Fade(element, { duration: ExitEffect.Duration });
		}
		else if (ExitEffect.Type=="slideup")
		{
			BoxItem.ActionEffect = new Effect.SlideUp(element, { duration: ExitEffect.Duration });
		}
		else
		{
			return this.ExitEffect_Complete(BoxID, true);
		}
		
		//post-transitions
		setTimeout("SMMessageBox.ExitEffect_Complete(" + BoxID + ", false);", ExitEffect.Duration*1000);
		
		return true;
	},
	
	ExitEffect_Prep: function(BoxID)
	{
		var BoxItem = this.BoxQueue[BoxID];
		var ExitEffect = BoxItem.params.ExitEffects[BoxItem.ExitEffectIndex];
		
		//return BoxItem.element;
		
		if (ExitEffect!=null && ExitEffect.Transition=="clonebase")
		{
			var clone = BoxItem.element.cloneNode(true);
			var originallyAbsolute = (BoxItem.element.getStyle('position') == 'absolute');
			if (!this._originallyAbsolute) 
				Position.absolutize(BoxItem.element);
			BoxItem.element.parentNode.insertBefore(clone, BoxItem.element);
			
			$(clone).setStyle({visibility:'hidden'});
			
			BoxItem.elementClone = $(clone);
			
			return BoxItem.element;
		}
		else
		{
			return BoxItem.element;
		}
		
	},
	
	ExitEffect_Complete: function(BoxID, force)
	{
		var BoxItem = this.BoxQueue[BoxID];
		if (!force && BoxItem.ActionEffect==null) return;
		
		//alert(BoxItem.ExitEffectIndex);
		var ExitEffect = BoxItem.params.ExitEffects[BoxItem.ExitEffectIndex];
		
		if (ExitEffect!=null && ExitEffect.Transition=="clonebase")
		{
			 //make clone the principal  after effect is complete
			BoxItem.element.remove();
			BoxItem.element = BoxItem.elementClone;
			BoxItem.elementClone = null;
		}
		if (ExitEffect!=null && ExitEffect.Transition=="destroy")
		{
			BoxItem.element.remove(); //destroy the object
			
			//../completely
			if (BoxItem.elementClone!=null)
				BoxItem.elementClone.remove();
			
			this.BoxQueue[BoxID] = Object.extend(this.BoxQueue[BoxID], {element: null});
		}
		
		BoxItem.ActionEffect=null;
		
		BoxItem.ExitEffectIndex++;
		if (BoxItem.ExitEffectIndex>=BoxItem.params.ExitEffects.length)
			BoxItem.ExitComplete=true;
		
		return true;
	},
	
	ExitEffect_Cancel: function(BoxID)
	{
		var BoxItem = this.BoxQueue[BoxID];
		if (BoxItem.ActionEffect==null) return false;
		
		BoxItem.ActionEffect.cancel();
		BoxItem.ActionEffect=null;
		
		//alert(BoxItem.ExitEffectIndex);
		var ExitEffect = BoxItem.params.ExitEffects[BoxItem.ExitEffectIndex];
		
		if (ExitEffect!=null && ExitEffect.Transition=="clonebase")
		{
			//undo cloning
			Position.relativize(BoxItem.element);
			BoxItem.elementClone.remove();
			BoxItem.elementClone = null;
		}
		
		BoxItem.ExitEffectIndex--;
		if (BoxItem.ExitEffectIndex<0)
			BoxItem.ExitEffectIndex=0;
		BoxItem.ExitComplete=false;
		
		return true;
	},
	
	OnDocumentScroll: function(e)
	{
		var topPos = this._Container.cumulativeScrollOffset()[1];
		
		if (this._Position.indexOf("top")>=0)
		{
			topPos += this._OFFSET_Y;
		}
		
		this._Container.setStyle({top:topPos + 'px'});
	},
	
	OnElementOut: function(e, BoxID)
	{
		var element = this.BoxQueue[BoxID].element;
		
		this._HoverElement = null;
		
		this.BoxQueue[BoxID].init = new Date();
		//go back up in the list
		if (BoxID<this._PollingID) this._PollingID=BoxID;
		this.ExitEffectPolling(false); //restart polling if necessary
	},
	
	OnElementEnter: function(e, BoxID)
	{
		var element = this.BoxQueue[BoxID].element;
		
		this._HoverElement = element;
		
		this.ShowItem(BoxID);
	},
	
	OnElementClick: function(e, BoxID)
	{
		var BoxItem = this.BoxQueue[BoxID];
		
		if (BoxItem.element)
			BoxItem.element.remove();
		if (BoxItem.elementClone)
			BoxItem.elementClone.remove();
	}
}

var SMImage={
	init: function(id, options){
		var element = $(id);
		if(!element){
			SMLayoutManager.Log.Error('[SMImage] Could not locate element.');
			return;
		}
		
		if (element.hasClassName("Pan"))
		{
			Object.extend(element, {PanSpeed: 1 });
			
			//panning is enabled. bind events
			Event.observe(element.down("img"), "mousedown", SMImage.StartPan.bindAsEventListener(this, element));
			Event.observe(element.down("img"), "mousemove", SMImage.HandlePan.bindAsEventListener(this, element));
			Event.observe(element.down("img"), "mouseup", SMImage.EndPan.bindAsEventListener(this, element));
			Event.observe(element, "mouseout", SMImage.EndPan.bindAsEventListener(this, element));
			
			if (isIE)
			{
				Event.observe(element.down("img"), "dragstart", SMImage.DisableImageDrag.bindAsEventListener(this));
			}
		}
		
		var image = element.down();		
		if(!image){
			SMLayoutManager.Log.Error('[SMImage] Could not locate image element.');
			return;
		}
		
		var sep = "?";
		
		if (image.src === null || image.src == "")
			return;
		
		if (image.src && image.src.indexOf("?")>0){
			sep = "&";
		}
		if (!options.AllowCaching){
			image.src=image.src + sep + "randomizednumbernoclientcache=" + (Math.random() * 10000000);
		}
		
		if (options.MaxWidth>0){
			if (image.getWidth() > options.MaxWidth){
				image.setStyle({width: options.MaxWidth + "px"});
			}
		}
		if (options.MaxHeight>0){
			if (image.getHeight() > options.MaxHeight){
				image.setStyle({height: options.MaxHeight + "px"});
			}
		}
		
		if (options.OnHoverCss != null && options.OnHoverCss != ""){
			Event.observe(image, "mouseover", SMImage.OnMouseHover.bindAsEventListener(this, image, options.OnHoverCss));
			Event.observe(image, "mouseout", SMImage.OnMouseHoverOut.bindAsEventListener(this, image, options.OnHoverCss));
		}
	},
	
	DisableImageDrag: function(e)
	{
		e.preventDefault();
		
		//alert('test');
	},
	
	StartPan: function(e, element)
	{
		e.preventDefault();
		
		Object.extend(element, {
				PanStart: true,
				PanX: e.pageX,
				PanY: e.pageY
			});
	},
	
	HandlePan: function(e, element)
	{
		if (element.PanStart)
		{
			var x = (element.PanX - e.pageX) * element.PanSpeed;
			var y = (element.PanY - e.pageY) * element.PanSpeed;
			
			//SMLayoutManager.Log.Trace(x + ", " + y);
			
			element.scrollTop += y;
			element.scrollLeft += x;
			
			//update last pos
			Object.extend(element, {
				PanX: e.pageX,
				PanY: e.pageY
			});
		}
	},
	
	EndPan: function(e, element)
	{
		Object.extend(element,
			{ PanStart: false }
			);
	},
	
	OnMouseHover: function(e, image, hoverCss){
		image.addClassName(hoverCss);
	},	
	OnMouseHoverOut: function(e, image, hoverCss){
		image.removeClassName(hoverCss);
	},	
	PostBack: function(id, action, options){
		SMLayoutManager.FireAjaxEvent(id, 'SMImage', {EventType: 'OnCallback', Action: action}, options);
		//SMLayoutManager.FireAjaxEventType(id, 'SMImage', 'OnCallback', { Action: action });
	}
}

/* TODO: move components into seperate JS */

var SMTerminalComponent=Class.create({
	Name: null,
	Options: null,
	
	initialize: function(id, options)
	{
		this.Name = id;
		this.Options = options;
		
		SMLayoutManager.RegisterControl(this);
		
		this.StartRefresh();
	},
	
	StartRefresh: function()
	{
		if (this.Options.Interval > 0)
			setTimeout("SMLayoutManager.GetControl('" + this.Name + "').UpdateDisplay();", this.Options.Interval * 1000);
	},
	
	UpdateDisplay: function()
	{
		SMLayoutManager.FireAjaxEvent(this.Name, 'SMTerminalComponent', { EventType: 'OnUpdateDisplay' }, {onSuccess: this.OnUpdateComplete });
	},
	
	OnUpdateComplete:function(e)
	{
		this.StartRefresh();
	}
});

var SMSelectableCalendar=Class.create({
	Name: null,
	options: null,
	_SelectedDay: null,
	
	initialize: function(id, options)
	{
		this.Name = id;
		this.options = options;
		
		SMLayoutManager.RegisterControl(this);
		
		this.InitCalendar();
	},
	
	InitCalendar: function()
	{
		Event.observe($(this.Name), 'click', this.OnSelectDay.bindAsEventListener(this));
		
		//see if there is a selected day
		var selections = $$("#" + this.Name + " .CMVDay.Selected");
		
		if (selections.length > 0)
			this._SelectedDay = selections[0];
	},
	
	OnSelectDay: function(e)
	{
		var selectedDay = Event.element(e).up(".CMVDay");
		
		if (selectedDay != null)
		{
			if (selectedDay.hasClassName("NotSelectable"))
				return; //abort
			
			if (this._SelectedDay != null)
				this._SelectedDay.removeClassName("Selected");
				
			selectedDay.addClassName("Selected");
			
			this._SelectedDay = selectedDay;
			
			var intDate = this._SelectedDay.getAttribute('rel');
			
			SMLayoutManager.FireAjaxEvent(this.Name, 'SMSelectableCalendar', { EventType: 'OnSelect', SelectedValue: intDate });
		}
	}
	
});


/**** --end components */

//first version hack together. this will use a proper event-based OO model later
var SMCheckBox={
	PostBack: function(name, element, value){
		SMLayoutManager.FireAjaxEvent(name, 'SMCheckBox', { EventType: 'OnChange', Value: value, Checked: element.checked }, { Blocking: true });
	}
}
 
//first version hack together. this will use a proper event-based OO model later
var SMComboBox={
	_GlobalLists: [],
	
	PostBack: function(element, options){
		var selectedItems = [];
		$A(element.options).each(function(o){ if(o.selected) selectedItems.push(o.value); });
		var BoxName=getName(element.id);
		SMLayoutManager.FireAjaxEvent(BoxName, 'SMComboBox', { EventType: 'OnSelectionChanged', SelectedValue: element.value, SelectedValues: selectedItems, MetaData: options.MetaData });
	},
	
	InitList: function(element, listName)
	{
		SMLayoutManager.Log.Trace("[SMComboBox.InitList] SharedList=" + listName);
		
		var selectedValue = null;
		if (element.selectedIndex > -1)
			selectedValue = element.options[element.selectedIndex].value;
		
		for(var i=0; i<SMComboBox._GlobalLists.length; i++)
		{
			if (SMComboBox._GlobalLists[i].Name == listName)
			{
				//found list
				var list = SMComboBox._GlobalLists[i].List;
				SMLayoutManager.Log.Debug("[SMComboBox.InitList] " + list.length);
				for(var x=0;x<list.length; x++)
				{
					var reselectFlag = false;
					if (selectedValue == list[x].Key)
					{
						element.options.remove(element.selectedIndex);
						selectedValue = null;
						
						reselectFlag = true;
					}
					element.options[element.options.length] = new Option(list[x].Value, list[x].Key, false, reselectFlag);
				}
				
				return;
			}
		}
		
		SMLayoutManager.Log.Error("[SMComboBox.InitList] Could not find SharedList=" + listName);
	},
	
	AddSharedList: function(name, listObj)
	{
		for(var i=0; i<SMComboBox._GlobalLists.length; i++)
		{
			if (SMComboBox._GlobalLists[i].Name == name)
			{
				SMComboBox._GlobalLists[i].List = listObj;
				return;
			}
		}
		
		SMComboBox._GlobalLists.push({Name: name, List: listObj });
	}
}

//first version hack together. this will use a proper event-based OO model later
var SMHelpBox={
	OnMouseOver: function(element, context, contextValue, enableReplace)
	{
		if ($(element).EditButton==null)
		{
			var tmpEditButton=document.createElement("DIV");
			element.appendChild(tmpEditButton);
			$(tmpEditButton).addClassName("SMHelpBox_EditButton")
			
			Object.extend(element, {EditButton:tmpEditButton, context:context,contextValue:contextValue});
			Event.observe($(element).EditButton, "click", SMHelpBox.Edit_OnClick);
		}
		
		Position.clone($(element), $(element).EditButton, {setWidth:false,setHeight:false});
		$(element).EditButton.setStyle({display:''});
		
		if (enableReplace)
		{
			Object.extend(element, {ReplaceElement:element});
		}
		else
		{
			Object.extend(element, {ReplaceElement:null});
		}
	},
	OnMouseOut: function(element)
	{
		$(element).EditButton.setStyle({display:'none'});
	},
	Edit_OnClick: function(e)
	{
	    var HelpHash = $(e.target.parentNode).context;
	    if (HelpHash==null || HelpHash=="") HelpHash=SMLayoutManager._PageInfo.HelpHash;
	    
		var PopupRef=SMLayoutManager.ShowPopup("HelpPopup", "/help_popup.aspx?context=" + HelpHash + "&value=" + $(e.target.parentNode).contextValue);
		
		PopupRef.ReplaceElement=$(e.target.parentNode).ReplaceElement;
		
		SMLayoutManager._Progress=new SMProgress();
		SMLayoutManager._Progress.show();
		
		Event.stop(e);
	}
}

var SMToolTip={
	Requests: [],
	
	ShowToolTip: function(id, options)
	{
		if ($("TTContainer_" + id) != null)
			$("TTContainer_" + id).remove();
		
		var container = document.createElement("DIV");
		container.id = "TTContainer_" + id;
		container.innerHTML = options.Content;
		
		document.body.appendChild(container);
		
		//probably should bind this to a requestID sometime
		var target=null;
		for(var i=SMToolTip.Requests.length-1; i>=0; i--)
		{
			if (SMToolTip.Requests[i].id == id)
			{
				target = SMToolTip.Requests[i].element;
			}
		}
		
		if (target == null) return;
		
		Position.includeScrollOffsets = true;
		var x = $(target).cumulativeOffset()[0];
		var y = $(target).cumulativeOffset()[1];
		
		var container_width = $(container).down(".SMToolTip").getWidth();
		var container_height = $(container).down(".SMToolTip").getHeight();
		
		y -= $(target).getHeight() + container_height;
		x -= container_width/2;
		
		$(container).setStyle({position:'absolute',top: (y + 'px'),left: (x + 'px')});
	},
	DispatchRequest: function(id, options, element)
	{
		//make up a request ID using tickcount
		var date = new Date();
		var requestID = date.getTime();
		
		this.Requests.push({requestID: requestID, id: id, options: options, element: element});
		setTimeout("SMToolTip.DelayedDispatch(" + requestID + ");", 1500);
		
		//create an event binding, if it doesent exist already, which clears requests when mouse moves off target
		if ($(element).ToolTipBinding==null)
		{
			Event.observe(element, "mouseout", SMToolTip.CancelRequests.bindAsEventListener(this, id));
		}
	},
	DelayedDispatch: function(requestID)
	{
		for(var i=0; i<this.Requests.length;i++)
		{
			if (this.Requests[i].requestID == requestID)
			{
				this.Dispath(this.Requests[i]);
			}
		}
	},
	CancelRequests: function(e, id)
	{
		if ($("TTContainer_" + id) != null)
			$("TTContainer_" + id).remove();
		
		for(var i=this.Requests.length - 1; i>=0;i--)
		{
			if (this.Requests[i].id == id)
			{
				this.Requests.splice(i,1);
			}
		}
	},
	Dispath: function(Request)
	{
		SMLayoutManager.FireAjaxEventType(Request.id, 'SMToolTip', 'OnGetToolTip', { Action:'get_tooltip', Context: Request.options.Context, ContextID: Request.options.ContextID });
	}
}

var SMTimePicker=Class.create({
	Name: null,
	_Options: null,
	
	initialize: function(id, options)
	{
		this.Name = id;
		this._Options = options;
		
		SMLayoutManager.RegisterControl(this);
		
		//hack: loading in the tooltip requires a delayed load
		setTimeout("SMLayoutManager.GetControl('" + this.Name + "').delayedInit();", 100);
	},
	
	delayedInit: function()
	{
		Event.observe("TimePickerHour_" + this.Name, "change", this.onChange.bindAsEventListener(this));
		Event.observe("TimePickerMinute_" + this.Name, "change", this.onChange.bindAsEventListener(this));
		Event.observe("TimePickerAMPM_" + this.Name, "change", this.onChange.bindAsEventListener(this));
	},
	
	onChange: function(e)
	{
		var time="";
		
		var tDate = new Date();
		
		var amPm = (document.getElementById("TimePickerAMPM_" + this.Name).value == "AM");
		
		tDate.setHours(document.getElementById("TimePickerHour_" + this.Name).value*1 + (amPm ? 0 : 12));
		tDate.setMinutes(document.getElementById("TimePickerMinute_" + this.Name).value*1);
		
		time=document.getElementById("TimePickerHour_" + this.Name).value;
		time+=":" + document.getElementById("TimePickerMinute_" + this.Name).value;
		time+=" " + document.getElementById("TimePickerAMPM_" + this.Name).value;
		
		if (this._Options.OnChangeInvokeTarget != null && this._Options.OnChangeInvokeTarget != '')
		{
			var control = SMLayoutManager.GetControl(this._Options.OnChangeInvokeTarget);
			control.IChangeDate(this.Name, tDate);
		}
		
		SMLayoutManager.FireAjaxEventType(this.Name, 'SMDatePicker', 'OnChange', { Value: '1/1/1900 ' + time });
	}
});

var SMDatePicker_calendar = null;

var SMDatePicker=Class.create({
	Name: null,
	
	initialize: function(id)
	{
		this.Name = id;
		
		if (document.loaded) {
			this.setup(this.Name);
		} 
		else {
			document.observe('dom:loaded', this.setup.bind(this));
		}
	},
	setup:function()
	{
		this._buildCalendar();
		
		Event.observe("SMDatePickerImage_" + this.Name, "click", this.showCalendar_click.bindAsEventListener(this));
		
		Event.observe("SMDatePicker_" + this.Name, "change", this.onChange.bindAsEventListener(this));
	},
	showCalendar_click: function(e)
	{
		SMLayoutManager.Log.Info("showCalendar(): SMDatePicker_" + this.Name);
		showCalendar("calendar", "SMDatePicker_" + this.Name, "calendar-container", Event.element(e), this.onChange.bindAsEventListener(this));
	},
	onChange: function(e)
	{
		this.ajaxUpdate();
	},
	ajaxUpdate: function()
	{
		SMLayoutManager.Log.Info("ajax(): " + this.Name);
		var element=document.getElementById("SMDatePicker_" + this.Name);
		var BoxName=this.Name;
		
		SMLayoutManager.FireAjaxEventType(BoxName, 'SMDatePicker', 'OnChange', { Value: element.value }, { Blocking: true });
	},
	
	_buildCalendar: function()
	{
		if (SMDatePicker_calendar == null)
		{
			var calContainer = document.createElement("DIV");
			calContainer.id = "calendar-container";
			document.body.appendChild(calContainer);
			
			$(calContainer).setStyle({width:'221px', position:'absolute', display:'none', zIndex:9999});
			$(calContainer).innerHTML = "	<b class=\"rtop\">";
			$(calContainer).innerHTML += "		<b class=\"r1\"></b> <b class=\"r2\"></b> <b class=\"r3\"></b> <b class=\"r4\"></b>";
			$(calContainer).innerHTML += "	</b>";
			$(calContainer).innerHTML += "	<div class=\"floating\" id=\"calendar\"></div>";
		}
	}
});

var SMTimer={
	_IntervalHandles: [],
	
	Init: function(id, options)
	{
		Object.extend($(id), { Options: options });
		
		if (options.Enabled)
		{
			if (!SMTimer.CheckIfRunning(id))
			{
				var intervalHandle = setInterval(SMTimer.OnTick.bindAsEventListener(this,id,options), options.Interval * 1000);
				SMTimer._IntervalHandles.push({id: id, Handle: intervalHandle});
			}
		}
		else
		{
			for(var i=SMTimer._IntervalHandles.length-1; i>=0; i--)
			{
				if (SMTimer._IntervalHandles[i].id == id)
				{
					clearInterval(SMTimer._IntervalHandles[i].Handle);
					SMTimer._IntervalHandles.splice(i, 1);
				}
			}
		}
	},
	
	CheckIfRunning: function(id)
	{
		for (var i=0; i<SMTimer._IntervalHandles.length; i++)
		{
			if (SMTimer._IntervalHandles[i].id == id)
				return true;
		}
		
		return false;
	},
	
	OnTick: function(e, id, options)
	{
		SMLayoutManager.FireAjaxEvent(id, 'SMTimer', { EventType: 'OnTick' });
	}
}

var SMTextBox={
	Init: function(id, options)
	{
		Object.extend($(id), { Options: options, OverlayEnabled:false });
		
		Event.observe($("TextBox_" + id), "focus", this.OnFocus.bindAsEventListener(this, id));
		Event.observe($("TextBox_" + id), "blur", this.OnBlur.bindAsEventListener(this, id));
		
		if ( $("TextBox_" + id).type == "password" ) {
			$(id).Options.OverlayMirror = true;
			
			var input = "<input style=\"color:#afafaf;\" ";
			var attrs = $("TextBox_" + id).attributes;
			var name, value;
			for( var i=0; i<attrs.length; i++ ) {
				name = attrs[i].name;
				value = $("TextBox_" + id).readAttribute( name );
				if ( typeof value === 'string' && value != '' ) {
					switch( name ) {
						case 'type': input += 'type="text" '; break;
						case 'id': input += 'id="' + attrs[i].value + '_Mirror"'; break;
						default: input += name + '="' + value + '" '; break;
					}
				}
			}
			input += " />";
			
			$("TextBox_" + id).insert({after:input});
			$("TextBox_" + id + "_Mirror").value = $(id).Options.OverlayText;
			
			Event.observe($("TextBox_" + id + "_Mirror"), "focus", this.OnMirrorFocus.bindAsEventListener(this, id));
		}
		
		this.OnBlur(null, id);
	},
	
	OnFocus: function(e, id)
	{
		if ($(id).OverlayEnabled)
		{
			$(id).OverlayEnabled = false;
			$("TextBox_" + id).value = "";
			$("TextBox_" + id).setStyle({'color':'#000'});
		}
	},
	
	OnMirrorFocus: function(e, id)
	{
		$("TextBox_" + id).show();
		$("TextBox_" + id + "_Mirror").hide();
		$("TextBox_" + id).focus();
	},
	
	SetFocus: function(id, delayed)
	{
		DisableFocusFirst = true;
		
		if (delayed)
		{
			setTimeout("SMTextBox.SetFocus('" + id + "', false);", 200);
		}
		else
		{
			$("TextBox_" + id).focus();
		}
	},
	
	OnBlur: function(e, id)
	{
		if ($("TextBox_" + id).value == "")
		{
			if ( $(id).Options.OverlayMirror ) {
				$("TextBox_" + id).hide();
				$("TextBox_" + id + "_Mirror").show();
			} else {
				$(id).OverlayEnabled = true;
				$("TextBox_" + id).value = $(id).Options.OverlayText;
				$("TextBox_" + id).setStyle({'color':'#afafaf'});
			}
		}
	},
	
	OnKeyPress:function(element,options){
		// TODO: perhaps this should be an option?  or maybe the name of this event should reflect that its intended for auto complete instead of key presses...
		if(!element.keyPressTimer){
			element.keyPressTimer=SMTextBox.KeyPressTimerCallback.bind(this, element, options).delay(0.5);
		}
	},
	KeyPressTimerCallback:function(element,options){
		SMTextBox.PostBack(element,options);
		element.keyPressTimer=null;
		//SMLayoutManager.Log.Info(arguments);
	},
	PostBack: function(element,options)
	{
		var eventType = 'OnTextChanged';
		var BoxName=getName(element.id);
		var value="";
		if (options!=null){
			value = options.value;
			eventType=options.eventType||eventType;
		}
		SMLayoutManager.FireAjaxEvent(BoxName, 'SMTextBox', { EventType: eventType, Text: this.GetText(BoxName), Value: value }, { Blocking: true });
	},
	
	GetText: function(idOrBoxName)
	{
		var id = idOrBoxName.replace("TextBox_");
		
		if ($(id)!=null && $(id).OverlayEnabled!=null && $(id).OverlayEnabled)
			return "";
		else
			return ($("TextBox_" + id).value);
	}
}

var SMContextGrid={
	PostValue: function(id,options)
	{
		var eventType = 'OnPostValue';
		
		SMLayoutManager.FireAjaxEvent(id, 'SMContextGrid', Object.extend({ EventType: eventType }, options), { Blocking: true, Progress: false });
	}
}

var SMTagControl={
	Init: function(id, options)
	{
		Object.extend($(id), {Options: options});
		this.HideAllTags(id);
		Event.observe( $($(id).Options.EntryBox) , "keyup", this.OnKeyUp.bindAsEventListener(this,id));
	},
    
	ShowControls: function(id)
	{
		$(id).down(".SMTagShowAddButton").setStyle({display:'none'});
		$(id).down(".SMTagControls").setStyle({display:''});
		$($(id).Options.EntryBox).focus();
	},
	
	HideControls: function(id)
	{
		$(id).down(".SMTagShowAddButton").setStyle({display:''});
		$(id).down(".SMTagControls").setStyle({display:'none'});
	},
	
	Close: function(id)
	{
		$("TextBox_" + $(id).Options.EntryBox).setValue( "" );
		this.UnFilterTags(id);
		this.HideAllTags(id);
		this.HideControls(id);
	},
	
	HideAllTags: function(id)
	{
		$(id).down(".SMTagAllTags").setStyle({display:'none'});
		$(id).down(".SMTagShowAddButton").setStyle({display:''});
        $(id).down(".SMTagShowAllButton").setStyle({display:''});
	},
	
	ShowAllTags: function(id)
	{
		$(id).down(".SMTagAllTags").setStyle({display:''});
		$(id).down(".SMTagShowAddButton").setStyle({display:'none'});
        $(id).down(".SMTagShowAllButton").setStyle({display:'none'});
	},
	
	InsertTag: function(id, tag, element)
	{
		/*
        var i = $("TextBox_" + $(id).Options.EntryBox);
		if (i.getValue() != '') i.setValue( i.getValue() + ", " );
        i.setValue( i.getValue() + tag );
		*/
		
		var tags = new Array();
		tags.push(tag);
		
		//just directly insert tag
		SMTagControl.PostBack(id, "add", {AddedTags: tags });
		
		//SMTagControl.FilterTags(id);
	},
	
	UnFilterTags: function(id)
	{
		var AllTags = $A( $($(id).Options.AllTags).childNodes );
		AllTags.each(function(tagItem)
			{
				if (tagItem.tagName!=null && tagItem.tagName.toLowerCase()=="div")
				{
					tagItem.setStyle({display:'inline'});
				}
			});
	},
	
	OnKeyUp: function(e, id)
	{
		this.UnFilterTags(id);
	},
	
	FilterTags: function(id)
	{
		var tags = $("TextBox_" + $(id).Options.EntryBox).getValue();
		if (tags == "") return;
		
		var tagList = tags.split(',');
		for(var i=0; i<tagList.length; i++)
		{
			this.FilterTag(id, tagList[i]);
		}
	},
	
	FilterTag: function(id, tag)
	{
		//tag = tag.trim;
		//alert('filer: .' + tag + '.');
		var AllTags = $A( $($(id).Options.AllTags).childNodes);
		//alert(AllTags.length);
		AllTags.each(function(tagItem)
			{
				if (tagItem.tagName!=null && tagItem.tagName.toLowerCase()=="div")
				{
					var tagLink = tagItem.down(".TagInsert");
					if (trim(tagLink.innerHTML) == trim(tag))
					{
						tagItem.setStyle({display:'none'});
						return;
					}
				}
			});
	},
	
	RemoveTag: function(id, tag, element)
	{
		this.PostBack(id,"remove",{RemovedTag: tag});
	},
	
	AddTags: function(id)
	{
		var tags = $("TextBox_" + $(id).Options.EntryBox).getValue();
		
		if (tags == "") return;
		$("TextBox_" + $(id).Options.EntryBox).setValue( "" );
		
		this.PostBack(id, "add", {AddedTags: tags.split(',')});
	},
	
	PostBack: function(id,action,tagData)
	{
		var EventType;
		if (action == "add")
			EventType = "OnAddTag";
		if (action == "remove")
			EventType = "OnRemoveTag";
			
		var EventParameters = { EventType: EventType, Action: action, SelectedTags: $(id).SelectedTags };
		Object.extend(EventParameters, tagData);
		
		SMLayoutManager.FireAjaxEvent(id, 'SMTagControl', EventParameters);
	}
}

 //first version hack together. this will use a proper event-based OO model later
 var SMLinkButton={
	_DisabledElements: [],
	_IsBound: false,
	
	PostBack: function(name, value, url, options){

		options = Object.extend({
			Progress: options.OnClick_ProgressBar
		}, options || {});
		
		SMLinkButton.DisableOnRequest(name, options);
		
		SMLayoutManager.FireAjaxEvent(name, 'SMLinkButton', {EventType: 'OnClick', Value: value}, options);
	},
	DisableOnRequest: function(name, options)
	{
		if (options.DisableElementsOnSubmit != null && options.DisableElementsOnSubmit != 'None')
		{
			SMLinkButton._DisabledElements = new Array();
			
			//disable elements according to option
			if (options.DisableElementsOnSubmit == 'ThisButton')
			{
				$(name).disabled=true;
				
				SMLinkButton._DisabledElements.push($(name));
			}
			else if (options.DisableElementsOnSubmit == 'AllInput')
			{
				var inputElements = $$('input');
				
				inputElements.each(function(element)
					{
						if (!element.disabled)
						{
							element.setAttribute('disabled','disabled');
							
							SMLinkButton._DisabledElements.push(element);
						}
					});
			}
			
			if (!SMLinkButton._IsBound)
			{
				document.observe("SMAjax:PreProcessCallBack", SMLinkButton.ReEnable);
				SMLinkButton._IsBound = true;
			}
		}
	},
	ReEnable: function()
	{
		SMLinkButton._DisabledElements.each(function(element)
			{
				element.disabled=false;
			});
		
		SMLayoutManager.Log.Info("[SMLinkButton.ReEnable] Re-enabled ", SMLinkButton._DisabledElements.length, " elements");
		
		SMLinkButton._DisabledElements = new Array();
	},
	setTitle: function(id, newTitle)
	{
		var button=$(id);
		var title_entity = button.down(1);
		if (title_entity != null)
			title_entity.innerHTML = newTitle;
	},
	getTitle: function(id)
	{
		var button=$(id);
		var title_entity = button.down(1);
		if (title_entity != null)
			return title_entity.innerHTML;
			
		return "";
	},
	SetFocus: function(id, delayed)
	{
		if (delayed)
		{
			setTimeout("SMLinkButton.SetFocus('" + id + "', false);", 200);
		}
		else
		{
			//$$("#" + id + " input").focus(); //TODO: figure out how to make this work
		}
	},
	Depress: function(element)
	{
		$(element).removeClassName("UnDepressed");
		$(element).addClassName("Depressed");
	},
	UnDepress: function(element)
	{
		$(element).removeClassName("Depressed");
		$(element).addClassName("UnDepressed");
	}
}

 //first version hack together. this will use a proper event-based OO model later
 var SMRadioGroup={
	PostBack: function(element, id)
	{
		//var BoxName=element.name.split('_')[1];
		
		SMLayoutManager.FireAjaxEvent(id, 'SMRadioGroup', { EventType: 'OnChange', SelectedValue: element.value });
	}
}

 //first version hack together. this will use a proper event-based OO model later
 var SMFileUpload={
	UploadControls: [],
	
	uploadTitle: null,
	uploadUploading: null,
	emptyMessage: null,
	zipMessage: null,
	
	Init: function(id, options)
	{
		var uploadControlInfo = SMFileUpload._FindUploadControl(id);
		
		
		
		if (uploadControlInfo == null)
		{
			SMFileUpload.UploadControls.push({ID: id});
		
			uploadControlInfo = SMFileUpload.UploadControls[SMFileUpload.UploadControls.length-1];
		}

		Object.extend(uploadControlInfo, options);
		
		Object.extend(uploadControlInfo,
			{
				UploadField: document.getElementById(uploadControlInfo.UploadFieldName),
				UploadButton: document.getElementById(uploadControlInfo.UploadButtonName),
				UploadURL: document.getElementById(uploadControlInfo.UploadURLName),
				UploadIframe: document.getElementById(uploadControlInfo.UploadIframeName)
			});
			
		SMFileUpload.uploadTitle = uploadControlInfo.UploadButton.getAttribute('valueText');
		SMFileUpload.uploadUploading =uploadControlInfo.UploadButton.getAttribute('valueUpload');
		SMFileUpload.emptyMessage = uploadControlInfo.UploadButton.getAttribute('emptyMessage');
		SMFileUpload.zipMessage = uploadControlInfo.UploadButton.getAttribute('zipMessage');
		
		SMLayoutManager.Log.Debug(SMFileUpload.zipMessage);
	},
	
	_FindUploadControl: function(id)
	{
		for (var i=0;i<SMFileUpload.UploadControls.length;i++)
		{
			if (SMFileUpload.UploadControls[i].ID == id)
			{
				return SMFileUpload.UploadControls[i];
			}
		}
		
		return null;
	},
	
	SelectFile: function(id)
	{
		//var uploadControlInfo = SMFileUpload._FindUploadControl(id);
	},
	
	UploadFile: function(id)
	{
		var uploadControlInfo = SMFileUpload._FindUploadControl(id);
		
		if (uploadControlInfo.UploadField.value == "")
		{
			alert(SMFileUpload.emptyMessage);
			return;
		}
		
		SMLayoutManager.StartWaiting();
		
		var unzip = true;
		
		if (!uploadControlInfo.NoZipConfirm && uploadControlInfo.UploadField.value.indexOf(".zip") > 0)
		{
			unzip = confirm(SMFileUpload.zipMessage);
		}
		
		document.forms[0].target = uploadControlInfo.UploadIframeName;
		document.forms[0].encoding = 'multipart/form-data';
		document.forms[0].action = '/asyncmedia/upload' + escape(uploadControlInfo.UploadURL.value) + "?UploadID=" + uploadControlInfo.UploadFieldName + "&unzip=" + unzip;
		
		document.forms[0].submit();
		
		uploadControlInfo.UploadButton.value = SMFileUpload.uploadUploading;
		uploadControlInfo.UploadButton.disabled = true;
		uploadControlInfo.UploadField.disabled = true;
		
		//return false; //this must be within binding def
	},
	
	Complete: function(id)
	{
		var uploadControlInfo = SMFileUpload._FindUploadControl(id);
		
		if (uploadControlInfo == null)
			return;
		
		uploadControlInfo.UploadButton.value = SMFileUpload.uploadTitle;
		uploadControlInfo.UploadButton.disabled = false;
		uploadControlInfo.UploadField.disabled = false;
		uploadControlInfo.UploadField.value = "";
		
		var result = null;
		
		try
		{
			var result = uploadControlInfo.UploadIframe.contentWindow.document.body.innerHTML.evalJSON();
		}
		catch (e)
		{
		}
		
		if (result != null)
		{
			SMLayoutManager.StopWaiting();
			
			SMLayoutManager.Log.Trace("[SMFileUpload] Got valid JSON response from upload operation");
			
			if (result.UploadStatus == 'success')
			{
				//some import operations can take a bit, show progress indicator to indicate user file is being processed into its destination
				SMLayoutManager.FireAjaxEventType(id, 'SMFileUpload', 'OnUploadSuccess', result, { Progress: true });
			}
		}
	}
 }

 // very simple lightwindow replacement used for displaying an inline element
var DialogBox=Class.create({
	box: null,
	initialize: function(id) {	
		this.createOverlay();

		this.box = $(id);
		this.box.show = this.show.bind(this);
		this.box.hide = this.hide.bind(this);

		this.parent_element = this.box.parentNode;

		var e_dims = Element.getDimensions(this.box);
		var b_dims = Element.getDimensions(this.overlay);
		this.box.setStyle({
			position: 'absolute',
			left: ((b_dims.width/2) - (e_dims.width/2)) + 'px',
			top: '12px',
			zIndex: this.overlay.style.zIndex + 1
		});
	},
	createOverlay: function() {
		if($('dialog_overlay')) {
			this.overlay = $('dialog_overlay');
		} 
		else {
			this.overlay = document.createElement('div');
			this.overlay.id = 'dialog_overlay';
			Object.extend(this.overlay.style, {
				position: 'absolute',
				top: 0,
				left: 0,
				zIndex: 90,
				width: '100%',
				backgroundColor: '#000',
				display: 'none'
			});
			document.body.insertBefore(this.overlay, document.body.childNodes[0]);
		}
	},
	moveDialogBox: function(where) {
		Element.remove(this.box);
		if(where == 'back')
			this.box = this.parent_element.appendChild(this.box);
		else
			this.box = this.overlay.parentNode.insertBefore(this.box, this.overlay);
	},
	show: function() {
		this.overlay.style.height = SMLayoutManager.ScreenY()+'px';
		this.moveDialogBox('out');
		this.overlay.onclick = this.hide.bind(this);
		this.selectBoxes('hide');
		
		// readjust the position
		this.box.setStyle({
			left: ((this.overlay.getWidth()/2) - (this.box.getWidth()/2)) + 'px'
		});
		
		new Effect.Appear(this.overlay, {duration: 0.1, from: 0.0, to: 0.3, afterFinish: this.showDialog.bind(this)});
	},
	showDialog:function(){
		this.box.style.display='';
	},
	hide: function() {
		this.selectBoxes('show');
		new Effect.Fade(this.overlay, {duration: 0.1});
		this.box.style.display = 'none';
		this.moveDialogBox('back');
		//$A(this.box.getElementsByTagName('input')).each(function(e){if(e.type!='submit')e.value=''});
	},
	selectBoxes: function(what) {
		$A(document.getElementsByTagName('select')).each(function(select) {
			Element[what](select);
		});

		if(what == 'hide')
			$A(this.box.getElementsByTagName('select')).each(function(select){Element.show(select)})
	}
});


var Protoload = {
	// the script to wait this amount of msecs until it shows the loading element
	timeUntilShow: 250,
	
	// opacity of loading element
	opacity: 0.8,

	// Start waiting status - show loading element
	startWaiting: function(element, className, timeUntilShow) {
		if (typeof element == 'string')
			element = document.getElementById(element);
		if (className == undefined)
			className = 'SMProgressWaiting';
		if (timeUntilShow == undefined)
			timeUntilShow = Protoload.timeUntilShow;
		
		element._waiting = true;
		if (!element._loading) {
			var e = document.createElement('div');
			(element.offsetParent || document.body).appendChild(element._loading = e);
            e.style.display = 'none';
			e.style.position = 'absolute';
			try {e.style.opacity = Protoload.opacity;} catch(e) {}
			try {e.style.MozOpacity = Protoload.opacity;} catch(e) {}
			try {e.style.filter = 'alpha(opacity='+Math.round(Protoload.opacity * 100)+')';} catch(e) {}
			try {e.style.KhtmlOpacity = Protoload.opacity;} catch(e) {}
			
			/*var zIndex = 0;
			if (window.UI)
				if (UI.zIndex)
					zIndex = ++UI.zIndex;
			if (!zIndex)
				zIndex = ++Protoload._zIndex;
			e.style.zIndex = zIndex;*/
		}
		element._loading.className = className;
		window.setTimeout((function() {
			if (this._waiting) {
				var left = this.offsetLeft, 
					top = this.offsetTop,
					width = this.offsetWidth,
					height = this.offsetHeight,
					l = this._loading;
					
				l.style.left = left+'px';
				l.style.top = top+'px';
				l.style.width = width+'px';
				l.style.height = height+'px';
				l.style.display = 'block';
			}
		}).bind(element), timeUntilShow);
	},
	
	// Stop waiting status - hide loading element
	stopWaiting: function(element) {
		if (element._waiting) {
			element._waiting = false;
			element._loading.parentNode.removeChild(element._loading);
			element._loading = null;
		}
	}
};

Element.addMethods(Protoload);
Object.extend(Element, Protoload);


var ValidationController=Class.create({
	options:null,
	initialize:function(id, options){
		this.id=id;
		this.options=Object.extend({
			initialValues:[]
		},options);
		if(this.options.initialValues){
			this.options.initialValues.each(function(u){
				this.handleUpdate(u);
			}.bind(this));
		}
	},
	update:function(updates){
		updates.each(function(u){
			this.handleUpdate(u);
		}.bind(this));
	},
	handleUpdate:function(u){
		SMLayoutManager.Log.Info('[ValidationController] Performing update.');
		var validationDisplay = $(u.controlId).up('.validated,.oValidated');
		if(validationDisplay){
			if ( u.valid ) {
				validationDisplay.removeClassName('invalid');
				validationDisplay.removeClassName('oInvalid');
			} else {
				validationDisplay.addClassName('invalid')
				validationDisplay.addClassName('oInvalid')
			}
			var msgDisplay=validationDisplay.select('.validationMessage,.oValidationMessage').first();
			if(msgDisplay){
				msgDisplay.update(u.message);
			}
		}
	}
});


var TemplatedList={
	InsertItem:function(listControlId,insertMode,item){
		if(insertMode=='before'){
			$(listControlId).insert({ top: item });
		}
		else{
			$(listControlId).insert({ bottom: item });
		}
	}
};
