// represents an album entry which is a visual representation of an album
// meant to be used within the album list control (below)
function Spa_AlbumEntry(
 p_strID,
 p_strSrc,
 p_iWidth,
 p_iHeight,
 p_strTitle,
 p_strCountDescription,
 p_strUpdatedDescription,
 p_strNormalClassName,
 p_strSelectedClassName,
 p_iBorderWidth,
 p_fncOnClickHandler,
 p_fncOnDragHandler
)
{
 var objThis = this;
 this.strID = p_strID;
 // indicates whether this entry is currently selected
 var blnSelected = false;
 // the main span
 var span = this.element = Spa.Dom.CreateElement(null, "span",
 "position", "relative",
 "overflow", "hidden",
 "width", "100%",
 "cursor", "hand",
 "borderWidth", p_iBorderWidth,
 "borderStyle", "solid"
 );
 span.className = p_strNormalClassName;
 span.title = p_strTitle;
 span.tabIndex = 0;
 Spa.Memory.CircularReference.Create(span, "objContainer", this);
 var img = Spa.Dom.CreateElement(span, "img");
 if (p_strSrc)
 {
 img.src = p_strSrc;
 }
 // the spanOuter will contain 2 divs
 // each div will contain a <nobr> tag with the actual text
 // divs are used so that the 2 lines of text actually appear on
 // 2 separate lines
 var spanText = Spa.Dom.CreateElement(span, "span",
 "fontSize", "100%",
 "position", "absolute"
 );
 // the overflow, textOverflow, and whiteSpace styles are necessary to show the ellipsis
 // Also, it is necessary to give the div tag an actual fixed width in order
 // for the ellipsis to appear
 var div1 = Spa.Dom.CreateElement(null, "div",
 "position", "relative",
 "overflow", "hidden",
 "textOverflow", "ellipsis",
 "whiteSpace", "nowrap"
 );
 var div2 = div1.cloneNode();
 div2.className = "smallFont";
 var div3 = div2.cloneNode();
 div1.className = "bold";
 div1.innerText = p_strTitle;
 div2.innerText = p_strCountDescription;
 div3.innerText = p_strUpdatedDescription;
 div3.style.display = "none";
 spanText.appendChild(div1);
 spanText.appendChild(div2);
 spanText.appendChild(div3);
 // relayout the elements within this AlbumEntry component
 function PositionElements()
 {
 var blnBigMode = (span.clientWidth > 200);
 // the max width of the image
 var iMaxImageSideLength = blnBigMode ? 48 : 32;
 // the amount of padding to the left and right of the image
 var iImageHorizontalPadding = blnBigMode ? 8 : 4;
 // the amount of padding on the top and bottom
 var iVerticalPadding = 4;
 // scale the image
 Spa.Dom.Image.Scale(img, p_iWidth, p_iHeight, iMaxImageSideLength, iMaxImageSideLength);
 Spa.Properties.Set(img.style,
 "position", "relative",
 "left", iImageHorizontalPadding + Math.floor((iMaxImageSideLength - img.width) / 2)
 );
 // only show div3 if theres enough room
 div3.style.display = blnBigMode ? "inline" : "none";
 // the height must be at least as big as the smallest contained element + the padding
 span.style.height = Math.max(spanText.clientHeight, iMaxImageSideLength) + (2 * iVerticalPadding);
 // center the image vertically
 img.style.top = Math.floor((span.clientHeight - img.height) / 2)
 // center the text vertically
 spanText.style.top = Math.floor((span.clientHeight - spanText.clientHeight) / 2);
 // place the text to the right of the image
 spanText.style.left = iMaxImageSideLength + (2 * iImageHorizontalPadding);
 // set the width of the text to the current width of the main span
 div1.style.width = div2.style.width = Math.max(0, span.clientWidth - spanText.style.pixelLeft);
 // set the margin below the title text
 div1.style.marginBottom = blnBigMode ? 0 : 0;
 }
 // used to initialize all the elements after this
 // element has been added to the DOM
 this.DomInit = this.ResizeHandler = PositionElements;
 // if the span resizes, reposition the elements
 Spa.Dom.Event.Set(span, "onresize", PositionElements)
 // if the text resizes, reposition the elements
 Spa.Dom.Event.Set(spanText, "onresize", PositionElements)
 // set the selection state of the AlbumEntry
 this.SetSelected = function(p_blnSelected)
 {
 // mark the selection state
 blnSelected = p_blnSelected;
 // set the class of the element
 span.className = blnSelected ? p_strSelectedClassName : p_strNormalClassName;
 }
 // get the selection state of the AlbumEntry
 this.GetSelected = function()
 {
 return(blnSelected);
 }
 // Mouse activity handlers
 // There are 2 events that must be fired, onClick, and onDrag
 // The p_fncOnDragHandler should be called when this item starts
 // to drag. However, we don't want to start a real "drag-and-drop"
 // operation from this component. So, we detect the "dragging" of
 // this control be hooking up to the mousedown, and mousemove events.
 // In onmousedown, the current X and Y coordinates of the mouse are saved
 // In onmousemove, if the mouse is currently down, and the corrdinates ever
 // change, then the p_fncOnDragHandler event is fired
 // If the mouseup occurrs before the mouse is moved, then the onclick event is fired
 // either the onclick or onmousemove is guaranteed to be called if the onmousedown occurs
 // used in the onmousemove event handler to know if the mouse went down
 // on this component
 var blnMouseDown = false;
 var iLastMouseX;
 var iLaastMouseY;
 function MouseDownHandler()
 {
 blnMouseDown = true;
 // screen coordinates are used because they're easier to use
 // it does not really matter as long as we're consistent with
 // which coordinates we use for tracking movement
 iLastMouseX = event.screenX;
 iLaastMouseY = event.screenY;
 }
 Spa.Dom.Event.Set(span, "onmousedown", MouseDownHandler)
 function KeyPressHandler()
 {
 switch(event.keyCode)
 {
 case 13: // V
 // If the user hits enter, then fire the click event
 Spa.Invoker(p_fncOnClickHandler, objThis);
 break;
 }
 }
 Spa.Dom.Event.Set(span, "onkeypress", KeyPressHandler)
 function ClickHandler()
 {
 if (blnMouseDown)
 {
 // reset the blnMouseDown state
 blnMouseDown = false;
 // fire the p_fncOnClickHandler event
 Spa.Invoker(p_fncOnClickHandler, objThis);
 }
 }
 Spa.Dom.Event.Set(span, "onclick", ClickHandler)
 function MouseMoveHandler()
 {
 if (blnMouseDown)
 {
 if ((iLastMouseX != event.screenX) || (iLaastMouseY != event.screenY))
 {
 // reset the blnMouseDown state
 blnMouseDown = false;
 // fire the p_fncOnDragHandler event
 Spa.Invoker(p_fncOnDragHandler, objThis);
 }
 }
 }
 Spa.Dom.Event.Set(span, "onmousemove", MouseMoveHandler)
}
// represents the visual item that is being dragged during
// a drag-and-drop operation
// p_elementToDrag: the element that should be dragged (this
// element will not actually be dragged, but a clone of it will)
function Spa_DragItem(p_elementToDrag, p_elementParent)
{
 var objThis = this;
 var element = null;
 var objDragRefPoint = null;
 this.Show = function()
 {
 // clone the element to drag
 element = p_elementToDrag.cloneNode(true);
 // make it somewhat transparent
 // make sure that it has the same dimensions as the p_elementToDrag passed in
 Spa.Properties.Set(element.style,
 "position", "absolute",
 "filter", "progid:DXImageTransform.Microsoft.Alpha(opacity=65)",
 "width", p_elementToDrag.style.pixelWidth,
 "height", p_elementToDrag.style.pixelHeight
 );
 // the offset from the element to the p_elementParent
 var objElementOffsetPoint = Spa.Dom.FindOffsetInParentElement(p_elementToDrag, p_elementParent);
 // the offset from the event source element to the p_elementParent
 var objEventSourceOffsetPoint = Spa.Dom.FindOffsetInParentElement(event.srcElement, p_elementParent);
 // this is the reference point that will be used during the calls to Update
 objDragRefPoint = new Spa.Point(
 objElementOffsetPoint.left - (objEventSourceOffsetPoint.left + event.offsetX),
 objElementOffsetPoint.top - (objEventSourceOffsetPoint.top + event.offsetY)
 );
 // make the first call to Update to set its initial position
 objThis.Update();
 // add this element to the parent
 p_elementParent.appendChild(element);
 }
 this.Update = function()
 {
 // the offset from the event source element to the p_elementParent
 var objEventSourceOffsetPoint = Spa.Dom.FindOffsetInParentElement(event.srcElement, p_elementParent, true);
 // update the left and top style properties, effectively moving the dragged item
 element.style.left = objDragRefPoint.left + objEventSourceOffsetPoint.left + event.offsetX;
 element.style.top = objDragRefPoint.top + objEventSourceOffsetPoint.top + event.offsetY;
 }
 this.Hide = function()
 {
 // remove the dragged item from the parent
 p_elementParent.removeChild(element);
 element = null;
 }
}
// A control which shows a list of album entries
// Allows the album entries to be rearranged via drag-and-drop
// p_fncOnClick: fired when an album is clicked (album ID passed as param)
// p_fncOnSequenceChange: fired when the sequence of albums has been changed
function Spa_AlbumList(
 p_fncOnSelectedChange,
 p_fncOnSequenceChange,
 p_blnAllowDragging
)
{
 var objThis = this;
 var iMaxHeight = 1;
 var iCount = 0;
 var objSelectedEntry = null;
 var htAlbumEntries = new Array();
 // style information
 var iCellSpacing = 3;
 var iBorderWidth = 1;
 var strBackgroundColor = "";
 var strBorderColor = "#8899BB";
 var strBorderSelectedColor = "#FF9999";
 var strItemNormalColor = "#F0F0F0";
 var strItemSelectedColor = "#FFFFCC";
 var strBorder = "solid " + iBorderWidth + " " + strBorderColor;
 var iInsidePadding = 0;
 // element span
 var spanElement = this.element = Spa.Dom.CreateElement(null, "span", "width", "100%");
 // container span
 var spanContainer = Spa.Dom.CreateElement(spanElement, "span",
 "position", "absolute",
 "width", "100%",
 "backgroundColor", strBackgroundColor,
 "padding", Spa.Dom.Styles.TrblValue(iInsidePadding)
 );
 // scrolling span
 var spanScrolling = Spa.Dom.CreateElement(spanContainer, "span",
 "position", "relative",
 "width", "100%",
 "overflow", "auto",
 "backgroundColor", strBackgroundColor
 );
 // table setup
 var table = Spa.Dom.CreateElement(spanScrolling, "table",
 "width", "100%"
 );
 Spa.Properties.Set(table,
 "cellPadding", 0,
 "cellSpacing", iCellSpacing
 );
 // the body of the table
 var tbody = Spa.Dom.CreateElement(table, "tbody");
 // Gap Row
 var rowGap = Spa.Dom.CreateElement(null, "tr");
 var cellGap = Spa.Dom.CreateElement(rowGap, "td",
 "border", strBorder
 );
 cellGap.innerHTML = "&nbsp;";
 var iClientHeight = 0;
 var iClientWidth = 0;
 // state for drag operation
 var rowBeingDragged = null;
 var objDragItem = null;
 var objDraggedEntry = null;
 var iLastGapIndex = -1;
 var iIndexBeingDragged = -1;
 var blnGapAdded = false;
 // resizes the entire album list
 function ResizeHandler()
 {
 if (iCount == 0)
 {
 // set the height to 1
 spanScrolling.style.height = 1;
 }
 // set the outermost spanElement's height to the scrollHeight of the
 // absolutely positioned spanScrolling, or the max height of the album list,
 // whichever is smaller
 spanElement.style.height = Math.min(spanScrolling.scrollHeight, iMaxHeight);
 // set the height of the scrolling span to be the height of the outermost element
 // the spanScrolling needs a fixed height so that the scrollbars appear
 spanScrolling.style.height = spanElement.style.height;
 // these are the scrollable dimensions of the albumlist for purposes
 // of the drag operation.
 iClientHeight = spanScrolling.scrollHeight - (2 * iBorderWidth) + (2 * iInsidePadding);
 iClientWidth = spanScrolling.scrollWidth - (2 * iBorderWidth) + (2 * iInsidePadding);
 // call the resize handler for each Album entry so that each entry can
 // resize itself
 for (var strID in htAlbumEntries)
 {
 htAlbumEntries[strID].ResizeHandler();
 }
 return(spanScrolling.scrollHeight > 0);
 }
 // Allows the max height of the element to be set
 this.SetMaxHeight = function(p_iMaxHeight)
 {
 iMaxHeight = p_iMaxHeight;
 ResizeHandler();
 }
 // Event Handler for an album starting to drag
 function DragHandler(p_objEntry)
 {
 if (p_blnAllowDragging)
 {
 // set the album entry that is currently being dragged
 objDraggedEntry = p_objEntry;
 // the dragged row is 2 parents above the album entry element
 rowBeingDragged = objDraggedEntry.element.parentNode.parentNode;
 iIndexBeingDragged = Spa_IndexOfNode(rowBeingDragged, tbody);
 // the gap should be the same height as the removed row
 cellGap.style.height = rowBeingDragged.clientHeight;
 // initiate the dragging of the table. It does not matter which item is being
 // dragged, as we are doing our own hit detection, and drop detection. In fact, the
 // only reason to initiate a dragDrop call is to get the side-effect of automatic
 // scrolling when the mouse moves to the edges of a scrollable region.
 table.dragDrop();
 }
 }
 // This function is called when the drag operation begins
 function DragStartHandler()
 {
 // this event will be called whenever anything is dragged into the table
 // region. However, we're only concerned if an actual row is being dragged.
 if (!rowBeingDragged)
 {
 return;
 }
 // create the element that will be dragged and show it
 // use the outermost spanElement as the parent, so that the dragged item
 // can "float" outside the scrollable region without causing the scrollable
 // region to scroll left or right.
 objDragItem = new Spa_DragItem(objDraggedEntry.element, spanElement);
 objDragItem.Show();
 }
 Spa.Dom.Event.Set(table, "ondragstart", DragStartHandler)
 // This function is called continuously as the drag operation occurrs over the table
 function DraggingHandler()
 {
 // this event will be called whenever anything is dragged into the table
 // region. However, we're only concerned if an actual row is being dragged.
 if (!rowBeingDragged)
 {
 return;
 }
 // update the dragItem location
 objDragItem.Update();
 }
 Spa.Dom.Event.Set(table, "ondrag", DraggingHandler)
 // function that gets called when the drag operation is complete
 function DragEndHandler()
 {
 // this event will be called whenever anything is dragged into the table
 // region. However, we're only concerned if an actual row is being dragged.
 if (!rowBeingDragged)
 {
 return;
 }
 // Hide the dragged item
 objDragItem.Hide();
 // if the gap is currently in the album list, then...
 if (blnGapAdded)
 {
 // replace the gap with the row being dragged
 tbody.replaceChild(rowBeingDragged, rowGap);
 }
 // fire the SequenceChange event if necessary
 // The mouse may have been released when it was outside the album list area,
 // effectively canceling the operation, or the row may have been dropped
 // back into its original spot. So, only fire the event if something changed.
 if ((iLastGapIndex != -1) && (iLastGapIndex != iIndexBeingDragged))
 {
 if (p_fncOnSequenceChange)
 {
 p_fncOnSequenceChange();
 }
 }
 // reset the drag state
 rowBeingDragged = null;
 objDragItem = null;
 objDraggedEntry = null;
 iLastGapIndex = -1;
 iIndexBeingDragged = -1;
 blnGapAdded = false;
 }
 Spa.Dom.Event.Set(table, "ondragend", DragEndHandler)
 // This event is hooked up to the outermost spanElement, and is
 // continuously fires for the entire drag operation
 // There is no corresponding ondragleave event hooked up, so this
 // function does its own hit tracking.
 function DragOverHandler()
 {
 // this event will be called whenever anything is dragged into the table
 // region. However, we're only concerned if an actual row is being dragged.
 if (!rowBeingDragged)
 {
 return;
 }
 window.event.returnValue=false;
 // this is the offset of the source element within the spanElement
 var objOffsetPoint = Spa.Dom.FindOffsetInParentElement(event.srcElement, spanElement);
 // the is the coordinates of the mouse event within the spanElement
 var objPoint = new Spa.Point(objOffsetPoint.x + event.offsetX, objOffsetPoint.y + event.offsetY + spanScrolling.scrollTop);
 // check to see whether we're in or out of the albulList region
 if ((objPoint.x < 0) || (objPoint.x > iClientWidth) ||
 (objPoint.y < 0) || (objPoint.y > iClientHeight))
 {
 // we're outside the region of the span
 if (blnGapAdded)
 {
 // if the gap has been added, then remove it
 // and put the dragged item back in place
 tbody.removeChild(rowGap);
 Spa_InsertNodeAt(rowBeingDragged, tbody, iIndexBeingDragged);
 blnGapAdded = false;
 iLastGapIndex = -1;
 }
 }
 else
 {
 // calculate the gapIndex
 // the gapIndex can range from 0 to (iCount-1)
 // it represents where the gap should be displayed on the list,
 // and thus where the dragged item will end up when dropped
 var iSectionHeight = iClientHeight / iCount;
 var iGapIndex = Math.floor((objPoint.y) / iSectionHeight);
 iGapIndex = Math.max(0, Math.min(iCount-1, iGapIndex));
 // if the iGapIndex has not changed since the last time,
 // then do not redraw it
 if ((iLastGapIndex != iGapIndex) || (!blnGapAdded))
 {
 // the gap index has changed, or the gap is currently not in the albumlist
 if (blnGapAdded)
 {
 // existing gap, so remove it
 rowGap.parentNode.removeChild(rowGap);
 }
 else
 {
 // actual row sill present, so remove it
 tbody.removeChild(rowBeingDragged);
 }
 // insert the gap at the gap index
 Spa_InsertNodeAt(rowGap, tbody, iGapIndex);
 blnGapAdded = true;
 iLastGapIndex = iGapIndex;
 }
 }
 }
 Spa.Dom.Event.Set(spanElement, "ondragover", DragOverHandler)
 // callback for an album entry being clicked
 function ClickHandler(p_objEntry)
 {
 // clicking an album does NOT toggle its selection.
 // so if it already selected, do not do anything
 if (objSelectedEntry != p_objEntry)
 {
 if (objSelectedEntry)
 {
 // if there is a previously selected item
 // then deselect it
 objSelectedEntry.SetSelected(false);
 }
 // select this entry and mark it as the selected entry
 if (p_objEntry)
 {
 p_objEntry.SetSelected(true);
 }
 objSelectedEntry = p_objEntry;
 // fire the callback
 Spa.Invoker(p_fncOnSelectedChange, p_objEntry ? p_objEntry.strID : null);
 }
 }
 this.GetAlbumEntryHeight = function()
 {
 var iMin = iCellSpacing + iBorderWidth;
 if (iCount > 0)
 {
 var objFirstEntry = this.GetAlbumEntryAt(0);
 return(iMin + objFirstEntry.element.clientHeight);
 }
 else
 {
 return(iMin);
 }
 }
 // get the number of albums in the list
 this.GetCount = function()
 {
 return(iCount);
 }
 // get the album at the given index
 this.GetAlbumEntryAt = function(p_iIndex)
 {
 var row = tbody.childNodes[p_iIndex];
 if (row)
 {
 return(row.childNodes[0].childNodes[0].objContainer);
 }
 return(null);
 }
 // get the album with the given ID
 this.GetAlbumEntryByID = function(p_strID)
 {
 return(htAlbumEntries[p_strID]);
 }
 // remove the given album
 this.RemoveAlbumEntry = function(p_objEntry)
 {
 if (p_objEntry)
 {
 if (objSelectedEntry == p_objEntry)
 {
 ClickHandler(null);
 }
 tbody.removeChild(p_objEntry.element.parentNode.parentNode);
 iCount--;
 ResizeHandler();
 }
 }
 // get the album that is selected
 this.GetSelectedAlbumEntry = function()
 {
 return(objSelectedEntry);
 }
 // set the album that is selected
 this.SetSelectedAlbumEntry = function(p_objEntry)
 {
 ClickHandler(p_objEntry);
 }
 // add an album entry to this list
 this.AddAlbumEntry = function(
 p_strID,
 p_strSrc,
 p_iWidth,
 p_iHeight,
 p_strTitle,
 p_strCountDescription,
 p_strUpdatedDescription,
 p_strNormalClassName,
 p_strSelectedClassName
 )
 {
 // create the new entry
 var objEntry = new Spa_AlbumEntry(
 p_strID,
 p_strSrc,
 p_iWidth,
 p_iHeight,
 p_strTitle,
 p_strCountDescription,
 p_strUpdatedDescription,
 p_strNormalClassName,
 p_strSelectedClassName,
 iBorderWidth,
 ClickHandler,
 DragHandler
 );
 // if this ID has already been added, then just return
 // cannot have a duplicate id
 if (htAlbumEntries[objEntry.strID])
 {
 return;
 }
 // increment the count of albums
 iCount++;
 // add the album entry to the hashtable for easy lookup
 htAlbumEntries[objEntry.strID] = objEntry;
 // add the new row, cell, and AlbumEntry element
 var row = Spa.Dom.CreateElement(tbody, "tr");
 var cell = Spa.Dom.CreateElement(row, "td");
 cell.appendChild(objEntry.element);
 }
 // used to initialize all the elements after this
 // element has been added to the DOM
 this.DomInit = function()
 {
 return(ResizeHandler());
 }
}
function Spa_AlbumListUserInterface(
 p_elementParent,
 p_aaobjData,
 p_strNormalClassName,
 p_strSelectedClassName,
 p_blnAllowDragging,
 p_fncOnSequenceChange,
 p_fncOnIndexChange,
 p_fncOnReady
 )
{
 this.Select = function(p_strID)
 {
 var objEntry = objAlbumList.GetAlbumEntryByID(p_strID);
 if (objEntry)
 {
 objAlbumList.SetSelectedAlbumEntry(objEntry)
 }
 }
 this.Remove = function(p_strID)
 {
 var objEntry = objAlbumList.GetAlbumEntryByID(p_strID);
 if (objEntry)
 {
 objAlbumList.RemoveAlbumEntry(objEntry);
 }
 }
 function SetAlbumHeight()
 {
 var iAlbumEntryHeight = objAlbumList.GetAlbumEntryHeight();
 var iNumberAlbumsToShow = Math.floor(p_elementParent.clientWidth * (2/3) / iAlbumEntryHeight);
 iNumberAlbumsToShow = Math.max(1, Math.min(3, iNumberAlbumsToShow));
 var iMinAlbumlistHeight = (iNumberAlbumsToShow * iAlbumEntryHeight) + Math.ceil(iAlbumEntryHeight / 2);
 objAlbumList.SetMaxHeight(iMinAlbumlistHeight);
 }
 function AlbumListLayoutInit()
 {
 objAlbumList.DomInit();
 p_elementParent.style.visibility = "visible";
 Spa.Dom.Event.Set(p_elementParent, "onresize", SetAlbumHeight);
 SetAlbumHeight();
 Spa.SetTimeout(p_fncOnReady, 1);
 }
 function ClickHandler(p_strAlbumID)
 {
 Spa.Invoker(p_fncOnIndexChange, p_strAlbumID);
 }
 function SequenceChangeHandler()
 {
 var iCount = objAlbumList.GetCount();
 var astrIDs = new Array(iCount);
 for (var i=0; i < iCount; i++)
 {
 astrIDs[i] = objAlbumList.GetAlbumEntryAt(i).strID;
 }
 Spa.Invoker(p_fncOnSequenceChange, astrIDs);
 }
 // make the parent invisible until it has layout
 p_elementParent.style.visibility = "hidden";
 // create the albumlist
 var objAlbumList = new Spa_AlbumList(ClickHandler, SequenceChangeHandler, p_blnAllowDragging);
 objAlbumList.SetMaxHeight(1);
 // add the album entry data
 for (var i=0; i < p_aaobjData.length - 0; i++)
 {
 objAlbumList.AddAlbumEntry(
 p_aaobjData[i][0],
 p_aaobjData[i][1],
 p_aaobjData[i][2],
 p_aaobjData[i][3],
 p_aaobjData[i][4],
 p_aaobjData[i][5],
 p_aaobjData[i][6],
 p_strNormalClassName,
 p_strSelectedClassName
 );
 }
 // add the album list to its parent
 p_elementParent.appendChild(objAlbumList.element);
 // setup the even to finish initialization after the parent has layout
 Spa.Dom.ExecuteWhenLayoutComplete(p_elementParent, AlbumListLayoutInit);
}
function StartSlideshow()
{
 var spa_iMaxImageHeight = 500;
 var spa_flOptimalAspectRatio = 3/4;
 //divSpaSlideshow.style.setExpression("height",
 //"Math.min(" + spa_iMaxImageHeight + ", Math.round(this.clientWidth * " + spa_flOptimalAspectRatio + "))"
// );
 var spa_objSlidshowUserInterface = null;
 var spa_objAlbumListInterface = null;
 var htAlbumIdToIndex = new Array();

 for (var i=0; i < spa_aaImageData.length - 0; i++)
 {

 htAlbumIdToIndex[spa_aaImageData[i][0]] = i;
 }
 var spa_strCurrentAlbumID = null;
 
 function Spa_NewAlbum()
 {//设置新建相册路径

 Spa.Navigation.Goto(spa_strNewAlbumUrl);
 }
 function Spa_EditAlbum()
 {
 // use spa_strCurrentAlbumID to build edit url
//设置编辑相册路径
 var strUrl = spa_strEditAlbumTemplateUrl.replace(spa_strAlbumIdReplacementToken, spa_strCurrentAlbumID);
 
 Spa.Navigation.Goto(strUrl);
 }
 function Spa_DeleteAlbum()
 {
 if (spa_strCurrentAlbumID)
 {
 var iIndex = htAlbumIdToIndex[spa_strCurrentAlbumID];
 var strConfirmation = Spa.String.Format(spa_strDeleteAlbumConfirmation, spa_aaImageData[iIndex][4]);
 if (Spa.Spaces.Confirm(strConfirmation, spa_strDeleteYes, spa_strDeleteNo))
 {
 var strID = spa_strCurrentAlbumID;
 // remove it from the list
 spa_objAlbumListInterface.Remove(strID);
 // use spa_strCurrentAlbumID to build edit url
 var strUrl = spa_strDeleteAlbumTemplateUrl.replace(spa_strAlbumIdReplacementToken, strID);
 Spa.Xml.SendRequest(strUrl);
 }
 }
 }
 function Spa_FullViewer()
 {
 var strUrl = spa_strFullViewTemplateUrl.replace(spa_strAlbumIdReplacementToken, spa_strCurrentAlbumID);
 Spa.Navigation.Goto(strUrl);
 }
 var spa_btnNewAlbum = new Spa_ElementButton(nobrSpaNewAlbum, Spa_NewAlbum, false, true);
 var spa_btnEditAlbum = new Spa_ElementButton(nobrSpaEditAlbum, Spa_EditAlbum, false, false);
 var spa_btnDeleteAlbum = new Spa_ElementButton(nobrSpaDeleteAlbum, Spa_DeleteAlbum, false, false);
 var spa_btnFullViewer = new Spa_ElementButton(document.all["imgSpaFull"], Spa_FullViewer, true, false);
 function Spa_SetSlideshowSize()
 { 
 // alert(divSpaSlideshow.style.cssText);
 //if(!divSpaSlideshow)
  //divSpaSlideshow=$('divSpaSlideshow');
  if(divSpaSlideshow.style)
  {
divSpaSlideshow.style.width = divSpaMini.clientWidth - 8;
 divSpaSlideshow.style.left = Math.floor((divSpaMini.clientWidth - divSpaSlideshow.clientWidth) / 2);
 divSpaSlideshow.style.height = Math.min(
spa_iMaxImageHeight,
 Math.round(divSpaSlideshow.clientWidth * spa_flOptimalAspectRatio)
);
}
 }
 Spa.Dom.ExecuteWhenLayoutComplete(divSpaMini, Spa_SetSlideshowSize);
 Spa.Dom.Event.Set(divSpaMini, "onresize", Spa_SetSlideshowSize);
 function Spa_AlbumListScriptLoaded()
 {
 function SequenceChangeHandler(p_astrIds)
 {
 function PostReturnHandler(p_xmlhttp)
 {
 }
 if (p_astrIds)
 {
 var strBody = p_astrIds.join(",");
 Spa.Xml.SendRequest(spa_strSequenceAlbumsUrl, strBody, PostReturnHandler);
 }
 }
 function IndexChangeHandler(p_strAlbumID)
 {
 // make the buttons enabled if there is an item selected
 // make them disabled if there is not item selected
 spa_btnEditAlbum.SetEnabled(!!p_strAlbumID);
 spa_btnDeleteAlbum.SetEnabled(!!p_strAlbumID);
 spa_btnFullViewer.SetEnabled(!!p_strAlbumID);
 spa_strCurrentAlbumID = p_strAlbumID;
 Spa_ShowAlbum();
 }
 function ReadyHandler()
 {
 if (spa_iStartingAlbumIndex != -1)
 {
 spa_objAlbumListInterface.Select(spa_aaImageData[spa_iStartingAlbumIndex][0]);
 }
 }
 spa_objAlbumListInterface = new Spa_AlbumListUserInterface(
 divSpaAlbumList,
 spa_aaImageData,
 "partsmb",
 "PicTab",
 spa_blnAllowDragging,
 SequenceChangeHandler,
 IndexChangeHandler,
 ReadyHandler
 );
 }
 function $(s){return document.getElementById(s);}
 function SPA_SlideshowViewerScriptLoaded()
 {
 function ReadyHandler()
 {
 if (!spa_blnShowAlbumHasBeenCalled)
     {Spa_ShowAlbum(); }
 }
 spa_objSlidshowUserInterface = new SPA_SlideshowPlayerUserInterface(
 /*divSpaSlideshow,*/
  $("divSpaSlideshow"),
 true,
 true,
 2000,
 $("imgSpaPlay"),
 $("imgSpaPause"),
 $("imgSpaStop"),
 $("imgSpaPrevious"),
 $("imgSpaNext"),
 spa_strLoadingPhoto,
 spa_strErrorLoadingPhoto,
 ReadyHandler
 );

 }
 var spa_blnShowAlbumHasBeenCalled = false;
 function Spa_ShowAlbum()
 {
   if (!spa_objSlidshowUserInterface)
   {
 return;
   }
 if (!spa_strCurrentAlbumID)
   {
 spa_objSlidshowUserInterface.Clear();
    }
 else
   {
 spa_blnShowAlbumHasBeenCalled = true;
 var iIndex = htAlbumIdToIndex[spa_strCurrentAlbumID];
 var strUrl = spa_strAlbumDataTemplateUrl.replace(spa_strAlbumIdReplacementToken, spa_strCurrentAlbumID);
 spa_objSlidshowUserInterface.RunWithXmlSource(strUrl);
   }
 }
 Spa_AlbumListScriptLoaded();

 SPA_SlideshowViewerScriptLoaded();
}
//Spa_AlbumListScriptLoaded();
