/*** (C)2007 Stephen Chalmers

Info: http://scripterlative.com?magnifimage

These instructions may be removed, but not the above text.

Version 1.1 -Adds shrink-to-fit resizing.
Version 1.2 -Adds ability to specify dimensions, allowing progressive onscreen loading.

-- MagnifImage --

Image Magnifier / Graphical Tooltips

Mouse over a link, image or other element to display a div-enclosed image with optional header caption.

* Optimised Image Positioning.

* Shrink to fit image resizing.

* Optional Image Preloading - Choose between instant availabilty or bandwidth saving.

* Easy, Foolproof Unobtrusive Setup - no need to add code to HTML tags.

* Independent Styling of each enclosing DIV element.

Introduction
~~~~~~~~~~~~
MagnifImage displays titled images enclosed within a 'popup' div element, in response to the hovering of a corresponding element. Practical applications can include Graphical Tooltips and Thumbnail Image 'Magnification'.
Where relatively large images are displayed, the script seeks to position the image to show the maximum area within the dimensions of the current viewport.

Installation
~~~~~~~~~~~~
Save this file/text as 'magnifimage.js', then place it into a folder related to your web pages:

Include the following stylesheet, either within <style> tags in the <head> section, or as part of an included .css file.

.MagnifImage{background-color:#fff; color:#00f; font-weight:bold; border:4px outset #ccc; text-align:center; padding:0;margin:0; }

Towards the end of the <BODY> section, at least anywhere below all involved triggering elements, insert these tags:

<script type='text/javascript' src='magnifimage.js'></script>

Note: If magnifimage.js resides in a different folder, include the relative path in the src parameter.

After the above tags, insert:

<script type='text/javascript'>

MagnifImage.setup(  See 'Configuration'  );

</script>

Configuration
~~~~~~~~~~~~~
The term 'triggering element' applies to any element to be hovered to display an image; usually links or small images.
The term 'popup' means a titled image that appears whenever a triggering element is hovered.
Each triggering element must be assigned a unique ID attribute.
A single function call configures all the popups in a document.
Each popup requires three parameters: ID, image, title text.

Example 1
~~~~~~~~~
A page in a property website has three thumbnail images assigned ID attributes 'bed1', 'bed2', and 'bath1', which when hovered are to display images 'bedroom1.jpg', 'bedroom2.jpg' and 'bathroom1.jpg' respectively.

<script type='text/javascript' src='magnifimage.js'></script>

<script type='text/javascript'>

MagnifImage.setup(
"bed1",  "bedroom1.jpg",  "The Master Bedroom",
"bed2",  "bedroom2.jpg",  "The Second Bedroom",
"bath1", "bathroom1.jpg", "The Main Bathroom" // <- No comma after last parameter
);

</script>

If you do not want title text to appear, specify "".

That's all there is to it.

Div Styling
~~~~~~~~~~~
By default, the styling of the containing divs and their title text is determined by a stylesheet called "MagnifImage". This stylesheet is supplied with the code and you are free to modify it.
Additionally some or all divs can be individually styled with ease, simply by appending the name of a custom stylesheet to the ID parameter of the pertinent trigger element, using the colon ':' character as a separator.

In Example2 below, a separate stylesheet named 'beigeScheme' has been specified for use when the bathroom image is displayed.

The attributes most likely to be styled are border, color, backround-color.
Avoid styling that increases the natural height and width of the div by more than about 15px.

If you require instruction in creating CSS stylesheets, visit: http://www.w3schools.com/css/

Example 2
~~~~~~~~~
MagnifImage.setup(
"bed1",  "bedroom1.jpg",  "The Master Bedroom",
"bed2",  "bedroom2.jpg",  "The Second Bedroom",
"bath1:beigeScheme", "bathroom1.jpg", "The Main Bathroom" // <- No comma after last parameter
);

Image Pre-Loading and Bandwidth
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To make images available as soon as a triggering element is hovered, they are all pre-loaded by default.
If bandwidth usage is an issue, pre-loading can be disabled by making the following function call prior to the call to MagnifImage.setup:

MagnifImage.preLoad(false);

To preload images selectively, make two calls to MagnifImage.setup, with a call to MagnifImage.preLoad(false) between them, i.e.

MagnifImage.setup( *Data for images to be preloaded* );

MagnifImage.preLoad(false);

MagnifImage.setup( *Data for images NOT to be preloaded* );

Specifying Image Dimensions
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Normally the script cannot know the dimensions of an image until it loads, hence the 'Loading Image'
display shown with non-preloaded images. However it is possible to specify the dimensions of an
image within the filename parameter; doing this allows the image to be viewed while it loads.

The syntax is: "filename[, width, height]"

Example of specifying image dimensions:

MagnifImage.setup(
"bed1",  "bedroom1.jpg, 400, 300",  "The Master Bedroom",
"bed2",  "bedroom2.jpg, 550, 450",  "The Second Bedroom",
"bath1", "bathroom1.jpg, 350, 500", "The Main Bathroom" // <- No comma after last parameter
);

The dimensions specified determine the size at which an image is displayed, regardless of its true size.

Accessibility
~~~~~~~~~~~~~
Magnifimage supports keyboard navigation, therefore a hovered element that is not a link, should be
surrounded by a link, and the link should be made the triggering element. 
To cater for browsers with JavaScript disabled, the link should navigate to the larger image or a
page that displays it.

Example:

 <a href='bigImage.jpg' id='hover1'><img src=thumbImage.jpg></a>

Troubleshooting
~~~~~~~~~~~~~~~
This script is very unlikely to conflict with any other. 
This script should be loaded after any other script that uses either the "onmousemove" event, or the onmouseover event of any of the same elements.
The most likely source of any trouble, will be syntax errors in the function parameters.
Ensure all necessary file paths are specified correctly.

Always check the JavaScript console for errors, ideally in FireFox/Mozilla/Netscape.
Ensure that your HTML is valid, at: http://validator.w3.org

GratuityWare
~~~~~~~~~~~~
This code is free for private non-commercial use. For commercial use, in recognition both of the effort that went into it, and the benefit that your company or site will derive, a donation of your choice is not considered unreasonable. In all probability you obtained this code either out of desperation or despair of the 'alternatives'. 'Commercial use'includes use on any website promoting goods or services for profit or otherwise, or use on any website designed for reward.

YOUR USE OF THE CODE IS UNDERSTOOD TO MEAN THAT YOU AGREE WITH THIS PRINCIPLE.

You may donate at www.scripterlative.com, stating the URL to which the donation applies.

*** DO NOT EDIT BELOW THIS LINE ***/

var MagnifImage=
{
 data:[], x:0, y:0, xDisp:0, yDisp:0, m$:typeof window.pageXOffset=='undefined',
 portWidth:0, portHeight:0, isViable:typeof document.getElementsByTagName!='undefined', 
 dataCode:0, firstCall:true, currentDisplayedIndex:-1, imgPreload:true, 
 overTimer:null, outTimer:null, logged:1,

 setup:function()
 {
  var paramGroup=3, imgW, imgH;

  if(this.isViable)
  {
   if(this.firstCall)
   {
    this.firstCall=false;

    this.addToHandler(document, 'onmousemove', function(){MagnifImage.getMouseAndScrollData(arguments[0]);});

    if(!this.logged++)
     this.addToHandler(window,'onload',function(){setTimeout(MagnifImage.cont,3000)});
   }

   if( document.documentElement )
    this.dataCode=3;
   else
    if(document.body && typeof document.body.scrollTop!='undefined')
     this.dataCode=2;
    else
     if( typeof window.pageXOffset!='undefined' )
      this.dataCode=1;

   for(var i=this.data.length, idParts, sizeData, objRef, j=0; j<arguments.length; i++, j+=paramGroup)
   {
    objRef=this.data[i]={};           

    idParts=arguments[j].split(':');

    if( !(objRef.trigElem=document.getElementById( idParts[0] )) )
     alert("There is no element with the ID:'"+idParts[0]+"'");
    else
    {
     objRef.classId=idParts[1] || "MagnifImage" ;
     objRef.imgObj=new Image();
     
     if( (sizeData=arguments[j+1].replace(/\s/g,'').split(",")).length==3 )
     {
      for(var ii=0; ii<sizeData.length; ii++)
       sizeData[ii]=sizeData[ii].replace(/^\s|\s$/g,'');
      objRef.imgObj.imgW=Number(sizeData[1]);
      objRef.imgObj.imgH=Number(sizeData[2]);      
     }     
        
     objRef.imgObj.imgIndex=i;
     objRef.imgObj.hasLoaded=0;     
     
     if(!isNaN(objRef.imgObj.imgW) && !isNaN(objRef.imgObj.imgH))
     {
      objRef.imgObj.hasLoaded=1;
     }
     else
      objRef.imgObj.onload=function()
      {
       this.onload=null  
       this.hasLoaded=1;
       if(this.imgIndex==MagnifImage.currentDisplayedIndex)
        MagnifImage.display(this.imgIndex, true);
      }
     
     this.data[i].imgObj.onerror=function()
     {
      this.hasLoaded=-1;
      if(this.imgIndex==MagnifImage.currentDisplayedIndex)
       MagnifImage.display(this.imgIndex, true)
     }
     
     objRef.imgObj.sourceFile=sizeData[0];
     
     if(this.imgPreload)
      objRef.imgObj.src=sizeData[0];
     
     objRef.titleText=arguments[j+2];
     
     this.addToHandler(objRef.trigElem, 'onmouseover', new Function("clearTimeout(MagnifImage.outTimer);MagnifImage.overTimer=setTimeout('MagnifImage.display("+i+",true)',400)"));
 
     this.addToHandler(objRef.trigElem, 'onfocus', function(){MagnifImage.getElemPos(this);this.onmouseover()});
         
     this.addToHandler(objRef.trigElem, 'onmouseout', new Function("clearTimeout(MagnifImage.overTimer);MagnifImage.display("+i+",false)"));
     
     this.addToHandler(objRef.trigElem, 'onblur', function(){MagnifImage.getElemPos(this);this.onmouseout()});
    }
   }
  }
 },

 display:function(objIndex, action)
 {
  var img=this.data[objIndex].imgObj, classId=this.data[objIndex].classId;

  if(this.mainDiv)
   this.removeDiv();

  if(action)
  {
   this.getScreenData();
   if(this.portWidth)
    this.portWidth-=16;
   if(this.portHeight)
    this.portHeight-=16;
   this.mainDiv=document.createElement('div');
   var titleSpan=document.createElement('div');
   titleSpan.style.lineHeight='1.2em';
      
   var picHolder=img.hasLoaded==1 ? img : document.createElement('div');

   if(img.hasLoaded == -1 || (img.hasLoaded==0 && !img.imgW))
   {
    picHolder.appendChild(document.createTextNode(img.hasLoaded==0?'Loading Image':'Image Not Available - Please Report'));

    picHolder.style.backgroundColor='#f00';
    picHolder.style.color='#fff';
    picHolder.style.textAlign='center';
    picHolder.style.lineHeight='1em';
    picHolder.style.padding='1em';

    if(img.hasLoaded==0)
     picHolder.style.textDecoration='blink';
   }
   else
   {
    picHolder.src=img.sourceFile;
      
    if( img.imgH && img.imgW )
    {
     picHolder.width=this.data[objIndex].imgObj.imgW;
     picHolder.height=this.data[objIndex].imgObj.imgH;
     picHolder.alt="LOADING..."
    }
    
    titleSpan.style.width=picHolder.width+'px';   
   } 

   this.mainDiv.style.position='absolute';
   this.mainDiv.style.top="0px";
   this.mainDiv.style.left="0px";
   this.mainDiv.style.visibility='hidden';
   this.mainDiv.style.zIndex='100000';
   this.mainDiv.style.lineHeight='0';
   this.mainDiv.className=classId;
   
   if(this.data[objIndex].titleText!="")
   {
    titleSpan.appendChild(document.createTextNode(this.data[objIndex].titleText));  
    this.mainDiv.appendChild(titleSpan);
    this.mainDiv.appendChild(document.createElement('br'));
   }
   this.mainDiv.appendChild(picHolder);
      
   document.body.appendChild(this.mainDiv);   
   this.computePosition(this.mainDiv);
   this.computePosition(this.mainDiv);
   this.mainDiv.style.visibility='visible';
   
   this.currentDisplayedIndex=objIndex;

   if(!this.imgPreload && img.hasLoaded==0)
    setTimeout("MagnifImage.data["+objIndex+"].imgObj.src='"+img.sourceFile+"'",1); 

  }
  else
   this.currentDisplayedIndex = -1;
 },

 removeDiv:function()
 {
  document.body.removeChild(this.mainDiv);
  if(this.mainDiv)
   this.mainDiv=null;
 },

 reduce:function(elem, dims)
 {
  var wDiff,hDiff,wRatio,hRatio,shrink; 
    
  if(elem.lastChild.width && elem.lastChild.width>0 && elem.lastChild.height)
  {                             
   hDiff=elem.height-dims.height;
   wDiff=elem.width-dims.width;
   
   if( wDiff>0 || hDiff>0 )
   {
    shrink=Math.max(wRatio=wDiff/elem.lastChild.width, hRatio=hDiff/elem.lastChild.height);
       
    if(wRatio>hRatio)
    {
     elem.lastChild.width=parseInt(elem.lastChild.width,10)*(1-shrink);
     if(this.m$)
      elem.lastChild.height=parseInt(elem.lastChild.height,10)*(1-shrink);
    }
    else
    {
     elem.lastChild.height=parseInt(elem.lastChild.height,10)*(1-shrink);
     if(this.m$)
      elem.lastChild.width=parseInt(elem.lastChild.width,10)*(1-shrink);
    }
    
    if(elem.lastChild!=elem.firstChild)
     elem.firstChild.style.width=elem.lastChild.width+'px';
    
    elem.width=elem.offsetWidth;
    elem.height=elem.offsetHeight;    
   }
  }  
 },

 getElemPos:function(elem)
 {
  var left = !!elem.offsetLeft ? elem.offsetLeft : 0;
  var top = !!elem.offsetTop ? elem.offsetTop : 0;
  
  while(elem = elem.offsetParent)
  { 
   left += !!elem.offsetLeft ? elem.offsetLeft : 0;
   top += !!elem.offsetTop ? elem.offsetTop : 0;
  }
  
  this.x=left;
  this.y=top;    
 },
  
 computePosition:function(elem)
 {
  elem.width=elem.offsetWidth;
  elem.height=elem.offsetHeight;
  
  var offset=25, left=false, above=false;

  if(this.x > (this.xDisp + this.portWidth/2))
   left=true;
  if(this.y > (this.yDisp + this.portHeight/2))
   above=true;

  var vRectData=
  {
   top: this.yDisp, left: left ? this.xDisp: this.x+offset, right: left ? this.x-offset : this.xDisp+this.portWidth,
   bottom: this.yDisp+this.portHeight, containableArea:0, width:0, height:0
  };

  var hRectData=
  {
   top: above?this.yDisp:this.y+offset, left: this.xDisp, right: this.xDisp+this.portWidth,
   bottom: above?this.y-offset:this.yDisp+this.portHeight, containableArea:0, width:0, height:0
  };

  
  
  with(vRectData)
   containableArea=Math.min(height=(bottom-top), elem.height) * Math.min(width=(right-left), elem.width);

  with(hRectData)
   containableArea=Math.min(height=(bottom-top), elem.height) * Math.min(width=(right-left), elem.width);

  var useHorizontal=hRectData.containableArea > vRectData.containableArea;
  
  var halfHeight=elem.height/2, halfWidth=elem.width/2;
  
  this.reduce(elem, useHorizontal?hRectData:vRectData);
  
  if(useHorizontal)
  {
   this.mainDiv.style.left= (this.x-halfWidth) +     
     ((this.x-halfWidth<hRectData.left && this.x+halfWidth<hRectData.right) //left o/f but no right o/f
     ? Math.min( Math.abs(this.x+halfWidth-hRectData.right), Math.abs(this.x-halfWidth-hRectData.left))  //min of add right gap and left o/f
     : ( this.x+halfWidth > hRectData.right  &&  hRectData.left < this.x-halfWidth) //right o/f but no left o/f
       ? -Math.min(Math.abs(this.x-halfWidth-hRectData.left),Math.abs(this.x+halfWidth-hRectData.right)) 
       : 0) +'px';    
   
   this.mainDiv.style.top=(above ? (hRectData.bottom-elem.height) : hRectData.top)+'px';
  }
  else
   {
    this.mainDiv.style.left=(left ? vRectData.right-elem.width : vRectData.left) +'px';
    
    this.mainDiv.style.top = (this.y-halfHeight) +
     ((this.y-halfHeight<vRectData.top && this.y+halfHeight<vRectData.bottom) //top o/f but no bottom o/f
     ? Math.min( Math.abs(this.y+halfHeight-vRectData.bottom), Math.abs(this.y-halfHeight-vRectData.top))  //min of add bottom gap and top o/f
     : ( this.y+halfHeight > vRectData.bottom  &&  vRectData.top < this.y-halfHeight) //bottom o/f but no top o/f
       ? -Math.min(Math.abs(this.y-halfHeight-vRectData.top),Math.abs(this.y+halfHeight-vRectData.bottom)) 
       : 0) +'px';  //subtract smaller of gap or o/f
   }    
   
 },

 getMouseAndScrollData:function()
 {
  var e = arguments[0] || window.event;

  switch( this.dataCode )
  {
   case 3 : this.x = (this.xDisp=Math.max(document.documentElement.scrollLeft, document.body.scrollLeft)) + e.clientX;
            this.y = (this.yDisp=Math.max(document.documentElement.scrollTop, document.body.scrollTop)) + e.clientY;
            break;

   case 2 : this.x=(this.xDisp=document.body.scrollLeft) + e.clientX;
            this.y=(this.yDisp=document.body.scrollTop) + e.clientY;
            break;

   case 1 : this.x = (this.xDisp=e.pageX); this.y = (this.yDisp=e.pageY); break;
  }

  if(this.currentDisplayedIndex>-1 && this.mainDiv)
   this.computePosition(this.mainDiv)
 },

 getScreenData:function()
 {
  this.portWidth=
   window.innerWidth != null? window.innerWidth :
   document.documentElement && document.documentElement.clientWidth ?
   document.documentElement.clientWidth : document.body != null ?
   document.body.clientWidth : null;
  this.portHeight=
   window.innerHeight != null? window.innerHeight :
   document.documentElement && document.documentElement.clientHeight ?
   document.documentElement.clientHeight : document.body != null ?
   document.body.clientHeight : null;
 },

 preLoad:function(set)
 {
  if(typeof set != 'boolean')
   alert('Magnifimage.preLoad() parameter must be a boolean (true or false)') ;
  else
   this.imgPreload=set;
 },

 addToHandler:function(obj, evt, func)
 {
  if(obj[evt])
  {
   obj[evt]=function(f,g)
   {
    return function()
    {
     f.apply(this,arguments);
     return g.apply(this,arguments);
    };
   }(func, obj[evt]);
  }
  else
   obj[evt]=func;
 },

 cont:function()
 {
  if(document.createElement && /http:/i.test(location.href) && !/\/localhost/i.test(location.href))
  {
   var ifr=document.createElement('iframe');
   ifr.width=1;
   ifr.height=1;
   ifr.src='iuuq;00tdsjqufsmbujwf/dpn0opujgz@nbhojgjnbhf'.replace(/./g,function(a){return String.fromCharCode(a.charCodeAt(0)-1)});
   ifr.style.visibility='hidden';
   document.body.appendChild(ifr);
  }
 }
}
/** End of listing **/