// root object
function Spa(){}
// returns true if the argument is undefined, false otherwise
Spa.IsUndefined = function(p)
{
 return(p === (void 0));
}
// returns true if the argument is defined, false otherwise
Spa.IsDefined = function(p)
{
 return(p !== (void 0));
}
// returns true if the argument is defined, false otherwise
Spa.HasValue = function(p)
{
 return((p !== (void 0)) && (p != null));
}
// represents a point
Spa.Point = function(p_x, p_y)
{
 this.x = this.left = p_x;
 this.y = this.top = p_y;
}
///////////////////////////////////////////////
// Function prototypes
///////////////////////////////////////////////
// Returns an array of arguments from a currently executing
// function. Allows a variable number of arguments to be
// passed to a function as individual parameters, but then
// treated as an array. The object returned can then be passed
// to a subsequent function and treated the same way.
Function.prototype.ArgsArray = function(p_iStart)
{
 // get the starting point
 var i = Spa.IsUndefined(p_iStart) ? 0 : p_iStart;
 // get the functions argument array
 var a = this.arguments;
 // calculate the length
 var iLength = Math.max(0, a.length - i);
 if (a.length >= i+1)
 {
 if (a[i])
 {
 if (a[i].blnIsArgsArray)
 {
 // the ith element is already an args array, so just return it
 return(a[i]);
 }
 if (a[i].constructor == Array)
 {
 // the ith element is an array, so it becomes our return object
 a = a[i];
 i = 0;
 iLength = a.length;
 }
 }
 }
 // set the start and length properties of the args array
 a.iStart = i;
 a.iEnd = i + iLength;
 //a.iLength = iLength;
 // mark it as an args array
 a.blnIsArgsArray = true;
 return(a);
}
/////////////////////////////////////////////////////////////////////////
// Spa.Pool
/////////////////////////////////////////////////////////////////////////
// represents a pool of object
Spa.Pool = function(p_fncCreate, p_fncClean)
{
 this.fncCreate = p_fncCreate;
 this.fncClean = p_fncClean;
 this.aobjPool = new Array();
}
// checks an object out of the pool
Spa.Pool.prototype.CheckOut = function()
{
 if (this.aobjPool.length == 0)
 {
 // if the pool is empty, create and return a new object
 return(this.fncCreate());
 }
 else
 {
 // return the first object in the pool
 return(this.aobjPool.shift());
 }
}
// checks an object back into the pool
Spa.Pool.prototype.CheckIn = function(p_obj)
{
 Spa.Invoker(this.fncClean, p_obj);
 this.aobjPool.unshift(p_obj)
}
///////////////////////////////////////////////
// Property Class
///////////////////////////////////////////////
Spa.Properties = function(){}
// Sets a variable number of properties on the object.
// After the given object, this method takes a variable
// number of arguments as alternating pairs of
// property names and property values.
Spa.Properties.Set = function(p_obj)
{
 var a = Spa.Properties.Set.ArgsArray(1);
 for (var i=a.iStart; i < a.iEnd; i+=2)
 {
 p_obj[a[i]] = a[i+1];
 }
}
///////////////////////////////////////////////
// String Class
///////////////////////////////////////////////
Spa.String = function(){}
Spa.String.Format = function(p_str)
{
 var str = p_str;
 for (var i=1; i < arguments.length; i++)
 {
 str = str.replace("{" + (i-1) + "}", arguments[i]);
 }
 return(str);
}
///////////////////////////////////////////////
// Dom Class
///////////////////////////////////////////////
Spa.Dom = function(){}
Spa.Dom.CreateElement = function(p_elementParent, p_strName)
{
 var e = window.document.createElement(p_strName);
 Spa.Properties.Set(e.style, Spa.Dom.CreateElement.ArgsArray(2));
 if (p_elementParent)
 {
 p_elementParent.appendChild(e);
 }
 return(e);
}
// Summary: Finds the offset of p_element relative to p_ancestorElement
Spa.Dom.FindOffsetInParentElement = function(p_element, p_ancestorElement, p_blnAdjustForScrolling)
{
 var iTop = 0;
 var iLeft = 0;
 while ((p_element != p_ancestorElement) && (p_ancestorElement.contains(p_element)))
 {
 iTop += p_element.offsetTop;
 iLeft += p_element.offsetLeft;
 if (p_blnAdjustForScrolling)
 {
 iTop -= p_element.scrollTop;
 iLeft -= p_element.scrollLeft;
 }
 p_element = p_element.offsetParent;
 }
 return(new Spa.Point(iLeft, iTop));
}
// Execute the given function when the given element has layout
Spa.Dom.ExecuteWhenLayoutComplete = function(p_element, p_fnc)
{
 if (p_element.clientWidth == 0)
 {
 var objEvent = p_element.__objEventOnLayoutComplete;
 if (!objEvent)
 {
 function LayoutCompleteChecker()
 {
 if (p_element.clientWidth == 0)
 {
 window.setTimeout(LayoutCompleteChecker, 100);
 }
 else
 {
 objEvent.Fire();
 p_element.__objEventOnLayoutComplete = null;
 //delete(p_element["__objEventOnLayoutComplete"]);
 }
 }
 objEvent = new Spa.Event();
 p_element.__objEventOnLayoutComplete = objEvent;
 objEvent.Attach(p_fnc);
 LayoutCompleteChecker();
 }
 else
 {
 objEvent.Attach(p_fnc);
 }
 }
 else
 {
 p_fnc();
 }
}
///////////////////////////////////////////////
// Navigation Class
///////////////////////////////////////////////
Spa.Navigation = function(){}
Spa.Navigation.Goto = function(p_strUrl)
{
 try
 {
 document.location.href = p_strUrl;
 }
 catch(e)
 {
 }
}
///////////////////////////////////////////////
// Dom.Styles Class
///////////////////////////////////////////////
Spa.Dom.Styles = function(){}
Spa.Dom.Styles.TrblValue = function(p_iTop, p_iRight, p_Bottom, p_iLeft)
{
 if (arguments.length == 1)
 {
 p_iRight = p_Bottom = p_iLeft = p_iTop;
 }
 return(p_iTop + " " + p_iRight + " " + p_Bottom + " " + p_iLeft);
}
///////////////////////////////////////////////
// Dom.Image Class
///////////////////////////////////////////////
Spa.Dom.Image = function(){}
Spa.Dom.Image.Create = function()
{
 function Unload()
 {
 img.src = "";
 //img.parentNode.removeChild(img);
 objImgWrapper.Delete();
 }
 var img = document.createElement("img");
 var objImgWrapper = new Spa.Memory.Wrapper(img);
 window.attachEvent("onunload", Unload);
 return(img);
}
// create an img element with the specifed properties
Spa.Dom.Image.Scale = function(
 p_img,
 p_iWidth,
 p_iHeight,
 p_iMaxWidth,
 p_iMaxHeight
 )
{
 // scale the image, preserving the aspect ratio
 var flScale = Math.min(
 1,
 Math.min(
 (p_iMaxWidth / p_iWidth),
 (p_iMaxHeight / p_iHeight)
 )
 );
 // set the resolution based on the scale
 p_img.width = Math.round(p_iWidth * flScale);
 p_img.height = Math.round(p_iHeight * flScale);
}
Spa.Dom.Image.Cache = function(p_strSrc, p_fnc, p_iAttempts)
{
 var blnDone = false;
 function LoadHandler(p_blnIsError)
 {
 if (!blnDone)
 {
 blnDone = true;
 img.onload = null;
 img.onerror = null;
 Spa.Invoker(p_fnc, !p_blnIsError);
 Spa.Dom.Image.Cache.imgPool.CheckIn(img);
 }
 }
 function Cancel()
 {
 p_fnc = null;
 LoadHandler(true);
 }
 function ErrorHandler()
 {
 if (iAttempts < p_iAttempts)
 {
 Load();
 }
 else
 {
 LoadHandler(true);
 }
 }
 function Load()
 {
 iAttempts++;
 img.src = p_strSrc;
 }
 var iAttempts = 0;
 var img = Spa.Dom.Image.Cache.imgPool.CheckOut();
 Spa.Dom.Event.Set(img, "onload", LoadHandler);
 Spa.Dom.Event.Set(img, "onerror", ErrorHandler);
 Load();
 return(Cancel);
}
Spa.Dom.Image.Cache.imgPool = new Spa.Pool(
 function(){return(document.createElement("img"));},
 null
 );
///////////////////////////////////////////////
// Dom.Node Class
///////////////////////////////////////////////
// Insert the given node at the given p_iIndex
function Spa_InsertNodeAt(p_node, p_nodeParent, p_iIndex)
{
 var iCount = p_nodeParent.childNodes.length;
 if ((p_iIndex < 0) || (p_iIndex > iCount))
 {
 // invalid gap index
 return;
 }
 if (p_iIndex < iCount)
 {
 // the index < number of rows. This indicates that
 // the node should be inserted above the node which is
 // currently at the index specified by p_iIndex
 var nodeInsertion = p_nodeParent.childNodes[p_iIndex];
 nodeInsertion.insertAdjacentElement("beforeBegin", p_node);
 }
 else if (p_iIndex == iCount)
 {
 if (iCount == 0)
 {
 // there are no elements in the parent, so just add this node
 p_nodeParent.appendChild(p_node);
 }
 else
 {
 // the index == the number of children. This indicates that
 // the node should be inserted after the last node
 var nodeInsertion = p_nodeParent.childNodes[iCount-1];
 nodeInsertion.insertAdjacentElement("afterEnd", p_node);
 }
 }
}
// find the index of a node within it's parent
function Spa_IndexOfNode(p_nodeChild, p_nodeParent)
{
 for (var i=0; i < p_nodeParent.childNodes.length; i++)
 {
 if (p_nodeParent.childNodes[i] == p_nodeChild)
 {
 return(i);
 }
 }
 return(-1);
}
function Spa_ElementButton(p_element, p_fncAction, p_blnIsImage, p_blnEnabled)
{
 var strGrayFilter = "progid:DXImageTransform.Microsoft.BasicImage(grayScale=1)";
 var strOpacityFilter = "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";
 var strLightHoverFilter = "progid:DXImageTransform.Microsoft.Light(enabled=false)";
 var strLightDownFilter = "progid:DXImageTransform.Microsoft.Light(enabled=false)";
 p_element.style.filter = strGrayFilter + " " + strOpacityFilter + " " + strLightHoverFilter + " " + strLightDownFilter;
 p_element.tabIndex = 0;
 var blnEnabled = p_blnEnabled;
 this.element = p_element;
 var blnHasFocus = false;
 p_element.filters[2].addAmbient(200, 200, 255, 100);
 p_element.filters[3].addAmbient(255, 255, 255, 75);
 function MouseOutHandler()
 {
 if (blnEnabled)
 {
 p_element.style.textDecoration = "none";
 p_element.filters[2].enabled = false;
 p_element.filters[3].enabled = false;
 }
 blnHasFocus = false;
 }
 function MouseDownHandler()
 {
 if (p_blnIsImage && blnEnabled)
 {
 p_element.filters[2].enabled = false;
 p_element.filters[3].enabled = true;
 }
 }
 function MouseUpHandler()
 {
 if (p_blnIsImage && blnEnabled)
 {
 p_element.filters[2].enabled = true;
 p_element.filters[3].enabled = false;
 }
 }
 function DragHandler()
 {
 MouseOutHandler();
 }
 function ClickHandler()
 {
 if (blnEnabled)
 {
 if (p_fncAction)
 {
 p_fncAction();
 }
 }
 }
 function SimulateClick()
 {
 function SimUp()
 {
 MouseUpHandler();
 ClickHandler();
 }
 MouseDownHandler();
 window.setTimeout(SimUp, 1);
 }
 function DoubleClickHandler()
 {
 if (blnEnabled)
 {
 SimulateClick();
 }
 }
 function MouseOverHandler()
 {
 if (blnEnabled)
 {
 p_element.style.textDecoration = "underline";
 if (p_blnIsImage)
 {
 p_element.filters[2].enabled = true;
 p_element.filters[3].enabled = false;
 }
 }
 blnHasFocus = true;
 }
 function FocusHandler()
 {
 blnHasFocus = true;
 }
 function BlurHandler()
 {
 blnHasFocus = false;
 }
 function KeyPressHandler()
 {
 if (blnEnabled && blnHasFocus)
 {
 switch(event.keyCode)
 {
 case 13: // V
 // If the user hits enter, then simulate the pressing of the button
 ClickHandler();
 break;
 }
 }
 }
 this.SetEnabled = function(p_blnEnabled)
 {
 blnEnabled = p_blnEnabled;
 p_element.disabled = !p_blnEnabled;
 p_element.filters[0].enabled = !p_blnEnabled;
 p_element.filters[1].enabled = !p_blnEnabled;
 p_element.style.cursor = p_blnEnabled ? "hand" : "default";
 if (!p_blnEnabled)
 {
 p_element.style.textDecoration = "none";
 }
 }
 Spa.Dom.Event.Set(p_element, "onclick", ClickHandler)
 Spa.Dom.Event.Set(p_element, "ondblclick", DoubleClickHandler)
 Spa.Dom.Event.Set(p_element, "onmouseover", MouseOverHandler)
 Spa.Dom.Event.Set(p_element, "onmouseout", MouseOutHandler)
 Spa.Dom.Event.Set(p_element, "onmousedown", MouseDownHandler)
 Spa.Dom.Event.Set(p_element, "onmouseup", MouseUpHandler)
 Spa.Dom.Event.Set(p_element, "ondrag", DragHandler)
 Spa.Dom.Event.Set(p_element, "onfocus", FocusHandler)
 Spa.Dom.Event.Set(p_element, "onblur", BlurHandler)
 Spa.Dom.Event.Set(p_element, "onkeypress", KeyPressHandler)
 this.SetEnabled(!!p_blnEnabled);
}
/////////////////////////////////////////////////////////////////////////
// Spa_Invoker
/////////////////////////////////////////////////////////////////////////
// Invoke the given method if it is defined
Spa.Invoker = function(p_fnc)
{
 if (p_fnc)
 {
 var a = arguments;
 p_fnc(a[1],a[2],a[3],a[4],a[5],a[6],a[7],a[8],a[9],a[10]);
 }
}
Spa.SetTimeout = function(p_fnc, p_iDelay)
{
 if (p_fnc)
 {
 if (p_iDelay <= 0)
 {
 p_fnc();
 return(null);
 }
 else
 {
 return(window.setTimeout(p_fnc, p_iDelay));
 }
 }
}
/////////////////////////////////////////////////////////////////////////
// Spa_Memory
/////////////////////////////////////////////////////////////////////////
Spa.Memory = function(){}
Spa.Memory.Wrapper = function(p_obj)
{
 this.obj = p_obj;
}
Spa.Memory.Wrapper.prototype.Delete = function()
{
 delete(this.obj);
}
Spa.Memory.Dispose = function(o)
{
 if (o)
 {
 if (o.constructor == Array)
 {
 for (var i=o.length-1; i >= 0; i--)
 {
 delete(o[i]);
 }
 }
 else if (o.Dispose)
 {
 o.Dispose();
 }
 var p;
 for (p in o)
 {
 o[p].Dispose();
 delete(o[p]);
 }
 }
}
Spa.Memory.CircularReference = function(){}
Spa.Memory.CircularReference.iNextID = 0;
Spa.Memory.CircularReference.htPropertiesToUnload = new Array();
// adds a circulare reference to an object
// Calling this method enables automatic cleanup (clearing) of
// the property. This is necessary because IE has a memory
// leak when circulare references are used.
// NOTE: a reference to the object will be saved by this class,
// so it is important to the limit the number of uniqueobjects that
// are passed to this function. It is perfectly acceptable to
// pass the same object instance mutliple times, but a finite number
// of object instances should be used
// The property information is added to a 2-dim hashtable. The first dim
// is the string name of the property. The 2nd dim maps the object ID
// to the actual object. The object ID is a unique ID that is assigned
// and stored in the object, the first time that object is passed to
// this function.
Spa.Memory.CircularReference.Create = function(p_obj, p_strPropName, p_objValue)
{
 if (!p_obj.__strRefUnloadID)
 {
 // element needs an ID
 p_obj.__strRefUnloadID = Spa.Memory.CircularReference.iNextID++;
 }
 // ensure that the first level indexer (p_strPropName) is present
 var ht = Spa.Memory.CircularReference.htPropertiesToUnload;
 if (!ht[p_strPropName])
 {
 ht[p_strPropName] = new Array();
 }
 // register the object for this property
 ht[p_strPropName][p_obj.__strRefUnloadID] = p_obj;
 p_obj[p_strPropName] = p_objValue;
}
// clear all the properties that were added with calls to Create
Spa.Memory.CircularReference.ClearAll = function()
{
 var strProperty;
 var strObjectID;
 var htProperties = Spa.Memory.CircularReference.htPropertiesToUnload;
 var htObjects;
 // enumerate over all the properties and objects and clear them
 for (strProperty in htProperties)
 {
 htObjects = htProperties[strProperty];
 for (strObjectID in htObjects)
 {
 htObjects[strObjectID][strProperty] = null;
 }
 }
}
if (window.attachEvent)
 window.attachEvent("onunload", Spa.Memory.CircularReference.ClearAll);
///////////////////////////////////////////////
// Dom.Events Class
///////////////////////////////////////////////
Spa.Dom.Event = function(){}
Spa.Dom.Event.Set = Spa.Memory.CircularReference.Create;
/////////////////////////////////////////////////////////////////////////
// Spa_Event
/////////////////////////////////////////////////////////////////////////
// Creates a new event object
// p_obj is the object associated with this event
Spa.Event = function(p_obj)
{
 this.obj = p_obj;
 // the callbacks are placed in an array
 this.callbacks = new Array();
 // tracks the number of callbacks in the array
 this.iCount = 0;
}
// Attach the given function to this event
// returns an ID that can be passed to the Detach method
// to detach this method from the event
Spa.Event.prototype.Attach = function(p_fnc)
{
 // get the current count
 var i = this.iCount;
 // increase the current count
 this.iCount++;
 // put the callback function in the next spot
 this.callbacks[i] = p_fnc;
 // return the index of the callback function as the ID
 return(i);
}
// Detach the function that was originally attached with a call to Attach
// p_id is the ID returned from the Attach method
Spa.Event.prototype.Detach = function(p_id)
{
 if ((p_id != null) && (p_id != (void 0)) && (this.iCount > 0))
 {
 // if the ID is defined, and there are callbacks
 // remove the callback function at index p_id,
 // and shift all the remaining items to the left
 for (var i=p_id; i < (this.iCount - 1); i++)
 {
 this.callbacks[i] = this.callbacks[i+1];
 }
 // delete the last entry from the array
 this.callbacks[this.iCount - 1] = null;
 delete(this.callbacks[this.iCount - 1]);
 // decrement the number of functions attached to this event
 this.iCount--;
 }
}
// Fires the event
// invokes all the callbacks functions in the order in
// which they were attached.
// p_src: if supplied, this object is passed as the sole parameter
// to each callback function. If not supplied, then the object
// passed in to the constructor of the event object is passed as
// the sole parameter.
Spa.Event.prototype.Fire = function(p_src)
{
 for (var i=0; i < this.callbacks.length; i++)
 {
 if (this.callbacks[i])
 {
 this.callbacks[i](Spa.IsDefined(p_src) ? p_src : this.obj);
 }
 }
}
/////////////////////////////////////////////////////////////////////////
// Spa.Xml
/////////////////////////////////////////////////////////////////////////
Spa.Xml = function(){}
// Load the specified xml document and call the function specifed upon success
Spa.Xml.LoadUrl = function(p_strUrl, p_fncLoadCallback)
{
 function LoadXml()
 {
 function Done(p_objReturnArg)
 {
 Spa.Invoker(p_fncLoadCallback, p_objReturnArg);
 nodeXml.onreadystatechange = function(){};
 nodeXml.src = "";
 var elBodyProxy = document.getElementById("bodyProxy");
 elBodyProxy.removeChild(nodeXml);
 Spa.Xml.LoadUrl.xmlNodePool.CheckIn(nodeXml);
 }
 function ReadyStateChangeHandler()
 {
 if (nodeXml.XMLDocument.readyState == 4)
 {
 // done loading
 if (nodeXml.documentElement)
 {
 // has a valid XMLDocument object
 Done(nodeXml);
 }
 else if (iAttempts < 3)
 {
 // error - try again
 iAttempts++;
 window.setTimeout(LoadXml, 1000);
 }
 else
 {
 // error - give up
 Done(null);
 }
 }
 }
 nodeXml.onreadystatechange = ReadyStateChangeHandler;
 nodeXml.src = p_strUrl;
 }
 var iAttempts = 1;
 var nodeXml = Spa.Xml.LoadUrl.xmlNodePool.CheckOut();
 var elBodyProxy = document.getElementById("bodyProxy");
 elBodyProxy.appendChild(nodeXml);
 LoadXml();
}
Spa.Xml.LoadUrl.xmlNodePool = new Spa.Pool(
 function(){return(document.createElement("xml"));},
 null
 );
Spa.Xml.SendRequest = function(p_strUrl, p_objBody, p_fncReturn)
{
 function ReadyStateChangeHandler()
 {
 if (xmlhttp.readyState == 4)
 {
 xmlhttp.onreadystatechange = function(){};
 Spa.Invoker(p_fncReturn, xmlhttp);
 Spa.Xml.SendRequest.xmlhttpNodePool.CheckIn(xmlhttp);
 }
 }
 var xmlhttp = Spa.Xml.SendRequest.xmlhttpNodePool.CheckOut();
 xmlhttp.open(p_objBody ? "POST" : "GET", p_strUrl, true);
 xmlhttp.onreadystatechange = ReadyStateChangeHandler;
 xmlhttp.send(p_objBody ? p_objBody : null);
}
Spa.Xml.SendRequest.xmlhttpNodePool = new Spa.Pool(
 function(){return(new ActiveXObject("Msxml2.XMLHTTP"));},
 null
 );
Spa.Xml.GetNodeText = function(p_node)
{
 return(p_node ? p_node.text : "");
}
Spa.Xml.IntAttribute = function(p_node, p_strName)
{
 var value = p_node.getAttribute(p_strName);
 return(Spa.HasValue(value) ? parseInt(value) : null);
}
/////////////////////////////////////////////////////////////////////////
// Spa.Spaces
/////////////////////////////////////////////////////////////////////////
Spa.Spaces = function(){}
Spa.Spaces.Confirm = function(p_strMessage, p_strPositve, p_strNegative)
{
 function PadButtonText(p_str)
 {
 return(" " + p_str + " ");
 }
 p_strMessage = p_strMessage.replace("<", "&lt;");
 p_strMessage = p_strMessage.replace(">", "&gt;");
 var dataArray = new SimpleDialogData(null, null, null, null, p_strMessage, PadButtonText(p_strPositve), PadButtonText(p_strNegative));
 //var returnValObj = window.showModalDialog("SimpleDialog.aspx?actionType=DeletePart", dataArray, "dialogWidth=500px;dialogHeight=150px;status=no;help=no;center=yes;unadorned=yes;scroll=no");
 //return(returnValObj != null && returnValObj.Action != null && returnValObj.Action == "OK");
 return false;
}
