/*

PHOTO MORPHER v1.0beta (c) 2004-2005 Angus Turnbull, http://www.twinhelix.com
Altering this notice or redistributing this file is prohibited.

*/

// This is a standard event-adding function I use in many scripts.

function addEvent(o, n, f, l)
{
 var a = 'addEventListener', h = 'on'+n;
 if (o[a] && !l) return o[a](n, f, false);
 if (o[h])
 {
  o._c |= 0;
  var b = '_b' + (++o._c);
  o[b] = o[h];
 }
 o[h] = function(e)
 {
  e = e || self.event;
  var r = true;
  if (o[b]) r = o[b](e) != false && r;
  o._f = f;
  r = o._f(e) != false && r;
  return r;
 }
};

// Object constructor; pass its own name, IDs of area and list, a CSS classname to
// apply to active list items, and a hash of further classnames to apply on load.
function PhotoMorpher(myName, areaID, listID, itemClass, applyClasses)
{
 this.myName = myName;
 this.areaID = areaID;
 this.listID = listID;
 this.itemClass = itemClass;
 this.applyClasses = applyClasses;

 this.list = null;
 this.area = null;
 this.images = [];
 this.activeImage = 0;
 this.currentItem = null;
 this.loaded = false;
 this.loading = false;

 // Some other settings you might want to tweak!
 this.fadeSteps = 5;
 this.fadeVal = 0;
 this.fadeTimer = null;

 this.autoPlay = false;
 this.autoDelay = 10000;
 this.autoPreload = true;
 this.autoTimer = null;

 // Run on page load.
 if (document.getElementById && document.createElement && document.documentElement)
  addEvent(window, 'load', new Function(myName + '.init()'));
};


PhotoMorpher.prototype.init = function() { with (this)
{
 // Retreive references to the photo list and area.
 list = document.getElementById(listID);
 area = document.getElementById(areaID);
 if (!list || !area) return;

 // Put another container div inside the photo area, so padding on the main area is OK.
 var container = document.createElement('div');
 container.style.position = 'relative';
 area.appendChild(container);
 // Create two IMG elements inside the container area.
 // Make one relatively positioned, the other absolutely positioned in the same place.
 // _pgWidth and _pgHeight are the stored original dimensions for the image.
 images[0] = document.createElement('img');
 images[0].style.position = 'relative';
 images[0].style.zIndex = 2;
 container.appendChild(images[0]);
 images[1] = document.createElement('img');
 images[1].style.position = 'absolute';
 images[1].style.left = images[1].style.top = '0px';
 images[1].style.zIndex = 1;
 container.appendChild(images[1]);

 // Now set some properties for those images.
 for (var i = 0; i < 2; i++)
 {
  // These will store the image's original dimensions.
  images[i]._pgWidth = images[i]._pgHeight = -1;
  images[i]._pgWidth = images[i]._pgHeight = -1;
  // Apply an onload event handler and CSS opacity.
  addEvent(images[i], 'load', new Function('clearTimeout(' + myName + '.fadeTimer); ' +
   myName + '.fadeTimer = setInterval("' + myName + '.fade(0)", 100)'));
  if (images[i].filters) images[i].style.filter += 'alpha(opacity=0)';
  else images[i].style.opacity = images[i].style.MozOpacity = 0;
 }

 // Apply onclick events to the list items.
 for (var i = 0; i < list.childNodes.length; i++)
 {
  if (list.childNodes[i].nodeName.toLowerCase() == 'li')
  {
   addEvent(list.childNodes[i], 'click',
    new Function('e', myName + '.display(this); if (e.stopPropagation) e.stopPropagation(); ' +
    'if (e.cancelable&&e.preventDefault) e.preventDefault(); e.returnValue=false; return false'));
  }
 }

 // For all the elements in applyClasses, set their classes now.
 for (var id in applyClasses)
 {
  var elm = document.getElementById(id);
  elm.className += (elm.className ? ' ' : '') + applyClasses[id];
 }

 // OK, we're done setting it up.
 loaded = true;
 if (this.onsetup) this.onsetup();
 // Display the image for the first item in the list.
 display(list.getElementsByTagName('li')[0]);
}};


// Accepts a reference to an item, returns a hash of details re: the link in the item.
PhotoMorpher.prototype.getItemDetails = function(item) { with (this)
{
 // Find the a link inside the item with both HREF and ID attributes.
 var link, links = item.getElementsByTagName('a');
 for (var i = 0; i < links.length; i++)
 {
  if (links[i].href && links[i].id)
  {
   link = links[i];
   break;
  }
 }
 if (!link) return {};

 // Retrieve the WIDTHxHEIGHT attributes from the link.
 var w, h, src = link.getAttribute('href');
 if (link.id.match(/(\d+)x(\d+)/)) w = parseInt(RegExp.$1), h = parseInt(RegExp.$2);
 if (!src || !w || !h) return {};

 return { 'src': src, 'width': w, 'height': h };
}};


// Accepts a reference to an item in the source list, displays the corresponding image.
PhotoMorpher.prototype.display = function(newItem) { with (this)
{
 if (!newItem || !loaded || loading) return;

 var isVeryFirst = currentItem ? 0 : 1;

 // Get the link details from the list, and make sure it's a different one.
 var item = this.getItemDetails(newItem);
 if (item.src == images[activeImage].src) return;

 // Set the 'loading' flag, swap the active image flag, stop any pending AutoPlay timer.
 loading = true;
 activeImage = 1 - activeImage;
 clearTimeout(autoTimer);

 // Swap the old/new item CSS classnames if applicable and set currentItem.
 if (itemClass)
 {
  if (currentItem) currentItem.className = currentItem.className.replace(
   new RegExp('\\s*' + itemClass + '$'), '');
  newItem.className += (newItem.className ? ' ' : '') + itemClass;
 }
 currentItem = newItem;

 var image = images[activeImage];
 // Record new image dimensions.
 image._pgWidth = item.width;
 image._pgHeight = item.height;
 // If this is the very first image, set the dimensions of the old image and the
 // container so it doesn't look silly.
 if (isVeryFirst)
 {
  area.style.width = (images[0]._pgWidth = item.width) + 'px';
  area.style.height = (images[0]._pgHeight = item.height) + 'px';
 }

 // Call an onbeforeload event if applicable.
 if (this.onbeforeload) this.onbeforeload();
 // Load this image (underneath still-visible image), its onload will call fade().
 image.src = item.src;
}};


// Fade/Slide animation for loaded images, re-calls itself until done.
PhotoMorpher.prototype.fade = function() { with (this)
{
 // Get references to the newImage and oldImage.
 var newI = images[activeImage];
 var oldI = images[1 - activeImage];

 // First call? Fire onload.
 if (!fadeVal && this.onload) this.onload();
 // Stop race conditions by checking end & resetting interval here.
 if (fadeVal > 100)
 {
  // Stop animation, reset state.
  clearTimeout(fadeTimer);
  fadeVal = 0;
  loading = false;
  if (oldI.filters && oldI.filters.length && oldI.filters.alpha)
  {
   newI.filters.alpha.enabled = false;
   oldI.filters.alpha.opacity = 0;
  }
  else oldI.style.opacity = oldI.style.MozOpacity = 0;
  // Autoplay enabled? Set a timeout and preload the next image if so.
  if (autoPlay)
  {
   autoTimer = setTimeout(myName + '.next()', autoDelay);
   if (autoPreload)
   {
    var nextItem = currentItem;
    do
    {
     nextItem = nextItem.nextSibling || nextItem.parentNode.firstChild;
    } while (nextItem.nodeType != 1);
    this.autoPreloadImage = new Image();
    autoPreloadImage.src = this.getItemDetails(nextItem).src;
   }
  }
  return;
 }

 // Animate! Also set container dimensions.
 if (!fadeVal || (oldI._pgWidth != newI._pgWidth) || (oldI._pgHeight != newI._pgHeight))
 {
  var newWidth = parseInt(oldI._pgWidth + fadeVal*(newI._pgWidth - oldI._pgWidth)/100);
  newI.style.width = oldI.style.width = newWidth + 'px';
  area.style.width = newWidth + 'px';

  var newHeight = parseInt(oldI._pgHeight + fadeVal*(newI._pgHeight - oldI._pgHeight)/100);
  newI.style.height = oldI.style.height = newHeight + 'px';
  area.style.height = newHeight + 'px';
 }
 // Opacity seetings, first MSIE...
 if (newI.filters && newI.filters.length && newI.filters.alpha)
 {
  if (!newI.filters.alpha.enabled) newI.filters.alpha.enabled = true;
  newI.filters.alpha.opacity = parseInt(fadeVal);
 }
 // ... then standard. I'm dividing by >100 as setting opacity=1 in Mozilla flickers.
 else newI.style.opacity = newI.style.MozOpacity = fadeVal/100.1;

 // Stack the new image on top so we can see it.
 newI.style.zIndex = 2;
 oldI.style.zIndex = 1;

 // Raise the fadeVal percentage property.
 fadeVal += 100/fadeSteps;
}};



// Navigate forwards and backwards through the list of images, wrapping at the ends.
PhotoMorpher.prototype.next = function() { with (this)
{
 if (!loaded) return;
 var newItem = currentItem;
 do
 {
  newItem = newItem.nextSibling || newItem.parentNode.firstChild;
 } while (newItem.nodeType != 1);
 display(newItem);
}};
PhotoMorpher.prototype.prev = function() { with (this)
{
 if (!loaded) return;
 var newItem = currentItem;
 do
 {
  newItem = newItem.previousSibling || newItem.parentNode.lastChild;
 } while (newItem.nodeType != 1);
 display(newItem);
}};

// Toggles autoplay functionality. Optionally pass a timer interval in milliseconds.
PhotoMorpher.prototype.play = function(timer) { with (this)
{
 if (timer) autoDelay = timer;
 autoPlay = !autoPlay;

 if (!autoPlay) clearTimeout(autoTimer);
 else next();
}};
