/***



These instructions may be removed, but not the above text.

Version 1.1 -Adds shrink-to-fit resizing.

-- 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* );

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


*** 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;

  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, j=0; j<arguments.length; i++, j+=paramGroup)
   {
    this.data[i]={};

    idParts=arguments[j].split(':');

    if( !(this.data[i].trigElem=document.getElementById( idParts[0] )) )
     alert("There is no element with the ID:'"+idParts[0]+"'");
    else
    {
     this.data[i].classId=idParts[1] || "MagnifImage" ;
     this.data[i].imgObj=new Image();
     this.data[i].imgObj.imgIndex=i;
     this.data[i].imgObj.hasLoaded=0;
     this.data[i].imgObj.onload=function()
     {
      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)
     };
     this.data[i].imgObj.sourceFile=arguments[j+1];
     if(this.imgPreload)
      this.data[i].imgObj.src=arguments[j+1];
     this.data[i].titleText=arguments[j+2];
     this.addToHandler(this.data[i].trigElem, 'onmouseover', new Function("clearTimeout(MagnifImage.outTimer);MagnifImage.overTimer=setTimeout('MagnifImage.display("+i+",true)',400)"));
     
     this.addToHandler(this.data[i].trigElem, 'onmouseout', new Function("clearTimeout(MagnifImage.overTimer);MagnifImage.display("+i+",false)"));
    }
   }
  }
 },

 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';
   titleSpan.style.paddingBottom='1em'; // add space between text and image - NMM 2007-12-17
   titleSpan.style.paddingTop='1em';
   var picHolder=document.createElement(img.hasLoaded==1?'img':'div');

   if(img.hasLoaded!=1)
   {
    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;
    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;    
   }
  }  
 },
 
 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 **/
