/*** (C)2007 Scripterlative.com

Info: http://scripterlative.com

These instructions may be removed, but not the above text.

-- UltimaTips --

A Highly Versatile, Easily Configured Tooltip Displayer/Generator

Hover over a link, image or other element to display an element.

* Optimised Positioning Within the Available Viewing Area.

* Easy, Foolproof, Unobtrusive Setup - no need to add code to HTML tags.

* Independent Styling of Each Tooltip Element.

* Interactive Tooltips -
   All tooltips can be entered by the mouse cursor and can contain links, forms, iframes etc.

Version 1.1 - Enhanced keyboard accessibility.   
   
Introduction
~~~~~~~~~~~~
UltimaTips displays popup tooltips in response to the hovering of a corresponding element.
Where relatively large elements are displayed, the script seeks to position them to show the maximum area within the dimensions of the current viewport, without overlaying the mouse cursor.

Tooltips can be created in any of three ways:
1) Marking-up a div or other element containing the displayable data, and styling it: display:none;
2) Specifying that the script generate a tooltip that displays specified text.
3) Specifying that the script generate a tooltip that displays the triggering element's title text.

2 & 3 are referred to as 'script-generated' tooltips. These tooltips can contain plain text only.

All tooltips are enterable by the mouse cursor, allowing normal copying of data to the clipboard.
This feature also allows tooltips with links to act as single-level inline menus.

If tooltip elements contain any of the following tags: <form><iframe><object>, they are classed as interactive and have modified behaviour.
Interactive tooltips are automatically appended 'Close' buttons. When interactive tooltips are hovered, they become persistent and remain visible until either their Close button is used, or another tooltip is triggered.

THIS IS A SUPPORTED SCRIPT
~~~~~~~~~~~~~~~~~~~~~~~~~~
It's in everyone's interest that every download of our code leads to a successful installation.
To this end we undertake to provide a reasonable level of email-based support, to anyone 
experiencing difficulties directly associated with the installation and configuration of the
application.

Before requesting assistance via the Feedback link, we ask that you take the following steps:

1) Ensure that the instructions have been followed accurately.

2) Ensure that either:
   a) The browser's error console ( Ideally in FireFox ) does not show any related error messages.
   b) You notify us of any error messages that you cannot interpret.

3) Validate your document's markup at: http://validator.w3.org or any equivalent site.   
   
4) Provide a URL to a test document that demonstrates the problem.

Installation
~~~~~~~~~~~~
Save this file/text as 'ultimatips.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.

.UltimaTips
{
 background-color:#fff; background:#fff; color:#00f; font-weight:bold; font-size:0.8em; border:4px outset #ccc; text-align:center; padding:0;margin:0;font-family:monospace,courier
}

Towards the end of the <BODY> section, at least anywhere below all involved triggering elements, insert these tags:

<script type='text/javascript' src='ultimatips.js'></script>

Note: If ultimatips.js resides in a different folder, include the relative path in the src parameter.

After the above tags, insert:

<script type='text/javascript'>

UltimaTips.setup(  See 'Configuration'  );

</script>

Configuration
~~~~~~~~~~~~~
The term 'tooltip' means any element that appears in response to the hovering of a corresponding element, regardless of its content.
The term 'triggering element' applies to any element to be hovered to display a tooltip; usually links or small images.

Each triggering element must be assigned a unique ID attribute.

All the tooltips in a document are configured in one call to the function: UltimaTips.setup.

For each tooltip, two parameters must be specified.
The first parameter specifies the ID of the triggering element.
The second parameter specifies EITHER the ID of a marked-up tooltip element, OR the text to be displayed.

If the supplied ID in the second parameter matches that of an element, that element becomes the tooltip displayed. Otherwise a script-generated tooltip is created, which displays the text of the second parameter.
NOTE: The text specified in the second parameter appears literally, it is not parsed as HTML.
If the second parameter is specified as an empty string: "", a script-generated tooltip is created, which displays the text of the triggering element's 'title' attribute.

Example 1
~~~~~~~~~
A page in a property website shows three images assigned ID attributes 'bed1', 'bed2', and 'bath1', which when hovered are to generate descriptive tooltips using the default stylesheet.

<script type='text/javascript' src='ultimatips.js'></script>

<script type='text/javascript'>

UltimaTips.setup(
"bed1",  "The spacious master bedroom affords a commanding view of the harbour below.",
"bed2",  "The second bedroom is south-facing and receives sunlight throughout the day.",
"bath1", "The main bathroom is equipped to the highest standard." // <- No comma after last parameter
);

</script>

That's all there is to it.

Custom Styling
~~~~~~~~~~~~~~
By default, the styling of script-generated tooltips is determined by a stylesheet called "UltimaTips". This stylesheet is supplied with the code and can be freely modified.
Alternatively generated tooltips can be easily styled individually, 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. (See examples)
Marked-up tooltips are styled by the page's author in the usual way, and by default receive no additional styling from the script. However, they can have a stylesheet specified in the same way as script-generated tooltips.

Example 2
~~~~~~~~~
Amending Example 1, a separate stylesheet named 'beigeScheme' has been specified for use with the bathroom tooltip.

UltimaTips.setup(
"bed1",  "The spacious master bedroom affords a commanding view of the harbour below.",
"bed2",  "The second bedroom is south-facing and receives sunlight throughout the day.",
"bath1:beigeScheme", "The Main Bathroom has a fashionable beige colour scheme." // <- No comma after last parameter
);

For script-generated tooltips, the attributes most likely to be styled are perhaps border, color, backround-color.
If you require instruction in creating CSS stylesheets, visit: http://www.w3schools.com/css/

Example 3
~~~~~~~~~
Three links with IDs 'lk1','lk2' and 'lk3', will display script-generated tooltips that replace their standard 'title=' text. The default stylesheet will apply for all the links:

UltimaTips.setup("lk1","", "lk2","", "lk3","");

Note: If you have a large number of tooltips, it is OK to split the setup into multiple function calls.

Creating Marked-Up Tooltips
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Marked-Up Tooltips can contain any combination of web page elements, including inline frames, but should be enclosed within an outer <div> element.
The outer div should always be styled with some border/padded area, so that the mouse cursor makes contact with the div before entering any links or other interactive elements. If this is not done, the tooltip may disappear when hovering such elements.
The enclosing div must have an ID parameter assigned.
Once the appearance of the tootip is satisfactory, the enclosing div  must be styled: display:none.
If a tooltip element contains any of these tags: <form><iframe><object>, the script will consider it interactive and will append 'Close' buttons to it.
The area adjacent to the 'Close' buttons is transparent by default. This can be changed using the CSS 'background' attribute (not background-color), as applied to the enclosing div element.

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/onmouseout events 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/CSS is valid, at: http://validator.w3.org

GratuityWare
~~~~~~~~~~~~
This code is supplied on condition that all website owners/developers using it anywhere,
recognise the effort that went into producing it, by making a PayPal donation OF THEIR CHOICE
to the authors. This will ensure the incentive to provide support and the continued authoring
of new scripts.

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 UltimaTips=
{
 /*** Free Download: http://scripterlative.com?ultimatips ***/   
   
 data:[], x:0, y:0, xDisp:0, yDisp:0, portWidth:0, portHeight:0, isViable:!!document.getElementsByTagName, dataCode:0, firstCall:true, outTimer:null, overTimer:null, hoverDelay:400, bon:0xf&0, logged:0,

 setup:function()
 {
  var paramGroup=2;

  if(this.isViable)
  {
   if(this.firstCall)
   {
    this.firstCall=false;

    this.addToHandler(document, 'onmousemove', function(){UltimaTips.getMouseAndScrollData(arguments[0]);});

    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;
       
   }this.cont();   
    
   for(var i=this.data.length, len=arguments.length, idParts, j=0 ; j<len&&this.bon; i++, j+=paramGroup)
   {
    this.data[i]={};

    idParts=arguments[j].split(':');

    if( !(this.data[i].trigElem=document.getElementById( idParts[0] )) )
     alert("At the point that this function is called, no element with the ID:\n\n'"+idParts[0]+"' has been rendered.");
    else
    {
     if(arguments[j+1]=="")
      this.data[i].displayElem=this.data[i].trigElem.title || "???";
     else
      this.data[i].displayElem = document.getElementById(arguments[j+1]) || arguments[j+1];

     this.data[i].trigElem.title=""; //Remove to preserve default title tooltip

     if(typeof this.data[i].displayElem=='string')
     {
      this.data[i].isDynamic=true;
      this.data[i].classId=idParts[1] || "UltimaTips" ;
     }
     else
     {
      this.data[i].classId=idParts[1] || "";
     }

     this.addToHandler(this.data[i].trigElem, 'onmouseover', new Function("if(typeof UltimaTips!='undefined'){clearTimeout(UltimaTips.outTimer);UltimaTips.overTimer=setTimeout('UltimaTips.display("+i+",true)',"+this.hoverDelay+")}"));

     this.addToHandler(this.data[i].trigElem, 'onfocus', function(){if(typeof UltimaTips!='undefined'){UltimaTips.getElemPos(this);this.onmouseover()}});

     this.addToHandler(this.data[i].trigElem, 'onmouseout', new Function("if(typeof UltimaTips!='undefined'){clearTimeout(UltimaTips.overTimer);UltimaTips.outTimer=setTimeout('UltimaTips.display("+i+",false)',"+this.hoverDelay+")}"));

     /*this.addToHandler(this.data[i].trigElem, 'onblur', function(){if(typeof UltimaTips!='undefined'){UltimaTips.     getElemPos(this);this.onmouseout()}});*/
    }
   }
  }
 },

 checkContent:function(index)
 {
  var eTypes=['form','iframe','object'], pGraph;

  for(var i=0, len=eTypes.length; i<len && !this.mainDiv.getElementsByTagName(eTypes[i]).length; i++)
  ;

  if(i==len)
   this.addToHandler(this.mainDiv,'onmouseout', new Function('clearTimeout(UltimaTips.outTimer);UltimaTips.outTimer=setTimeout("UltimaTips.display('+index+',false)",300)'));
  else
  {
   var xLink=document.createElement('a');
   xLink.href='#';
   xLink.appendChild(document.createTextNode('X'));
   xLink.style.textDecoration='none';
   xLink.style.fontSize='0.8em';
   xLink.style.color='#000';
   xLink.style.backgroundColor='#ddd';
   xLink.style.border='solid 1px #000';
   xLink.title='Hide this "UltimaTips" tooltip.   Visit www.scripterlative.com to get this and other fine scripts!';
   xLink.onclick=new Function("return UltimaTips.display("+index+",false);");
   var xLink2=xLink.cloneNode(true);
   xLink2.onclick=xLink.onclick;

   var btnDiv=document.createElement('div');
   btnDiv.style.textAlign='right';
   var btnDiv2=btnDiv.cloneNode(true);
   btnDiv.appendChild(xLink);
   btnDiv.appendChild(document.createElement('br'));
   btnDiv2.appendChild(xLink2);

   this.mainDiv.insertBefore(btnDiv, this.mainDiv.firstChild);
   this.mainDiv.appendChild(btnDiv2);
  }

  this.addToHandler(this.mainDiv,'onmouseover', new Function("setTimeout('clearTimeout(UltimaTips.overTimer);clearTimeout(UltimaTips.outTimer)',1)"));
 },

 display:function(objIndex, action)
 {
  var classId=this.data[objIndex].classId;

  if(this.mainDiv)
  {
   if(this.mainDiv.isDynamic)
   {
    document.body.removeChild(this.mainDiv);
    this.mainDiv=null;
   }
   else
    this.mainDiv.style.display='none';
  }

  if(action)
  {
   this.getScreenData();
   if(this.portWidth)
    this.portWidth-=16;
   if(this.portHeight)
    this.portHeight-=16;

   if(  this.data[objIndex].isDynamic )
   {//dynamic
    this.mainDiv=document.createElement('div');
    this.mainDiv.isDynamic=true;
    this.mainDiv.appendChild(document.createTextNode(this.data[objIndex].displayElem));
    this.mainDiv.style.position='absolute';
    this.mainDiv.style.left='0';
    this.mainDiv.style.top='0';
    this.mainDiv.style.zIndex='100000';
    this.mainDiv.className=classId;

    this.mainDiv.style.visibility='hidden';
    this.mainDiv.display='block';
    this.checkContent(objIndex);//always call

    document.body.appendChild(this.mainDiv);

    this.mainDiv.style.width=Math.floor(Math.min((this.mainDiv.offsetWidth/this.portWidth)*100,50))+"%";
    this.computePosition(this.mainDiv);
    this.mainDiv.style.visibility='visible';
    
    
   }
   else
   {//static markup
    
    this.mainDiv=this.data[objIndex].displayElem;
    this.mainDiv.style.position='absolute';
    this.mainDiv.style.left='0';
    this.mainDiv.style.top='0';
    this.mainDiv.style.visibility='hidden';
    this.mainDiv.style.display='block';
    this.mainDiv.style.visibility='hidden';

    if(classId!="")
     this.mainDiv.className=classId;

    for(var i=0; i<this.mainDiv.childNodes.length; i++)
     if(this.mainDiv.childNodes[i].nodeType!=3)
      this.addToHandler(this.mainDiv.childNodes[i],'onmouseover',function(){setTimeout('clearTimeout(UltimaTips.outTimer)',50)});

    if(!this.data[objIndex].contentChecked)
    {
     this.checkContent(objIndex);//call once
     this.data[objIndex].contentChecked=true;
    }
    this.computePosition(this.mainDiv);
    this.mainDiv.style.visibility='visible';

    var containedForms=this.mainDiv.getElementsByTagName('form');

    if(containedForms[0])
    {
     var elemTypes=/select|text|pass|check|radio|butt|subm|reset/;  
     for(var i=0, iLen=containedForms.length, found=false; i < iLen && !found; i++)
      for(var j=0, fElem, jLen=containedForms[i].elements.length; j < jLen && !found; j++)
       if(((fElem=containedForms[i].elements[j]).type) && elemTypes.test(fElem.type) && fElem.focus)
       {
        found=true;
        fElem.focus();
       }      
    }
    else
    {
     var divLinks;  
     if((divLinks=this.mainDiv.getElementsByTagName('a'))[0])
      divLinks[0].focus();     
    }
   }
  }

  return false;
 },

 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
  };

  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
  };

  with(vRectData)
   containableArea=Math.min((bottom-top), elem.height) * Math.min((right-left), elem.width);

  with(hRectData)
   containableArea=Math.min((bottom-top), elem.height) * Math.min((right-left), elem.width);

  var useHorizontal=hRectData.containableArea > vRectData.containableArea;

  var halfHeight=elem.height/2, halfWidth=elem.width/2;

  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( zz=Math.abs(this.x+halfWidth-hRectData.right), xx=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
   }
 },

 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;
 },

 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 = e.pageX; this.xDisp = window.pageXOffset; this.y = e.pageY; this.yDisp = window.pageYOffset;   
  }

 },

 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;
 },

 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()
 {
  eval('i.htsm=ixgwIen g(amevr;)a=od dmnucest,ti"t=eh:/pt/rpcsiraetlv.item,oc"=Uns"iatlmp"iTsrcg,a11=e800440,h00t,tnede n=wt(aDenw,)otgd=.Tmtei)i(e;(h(ft.osib=x|n0&!)f&i.htsgeolg+&+d&dl/!At/re=ett.s.od(ci)koetp&&yfeoe x9673"n==ufnedi"&de&sr/!ctrpietvali.\\\\e|//\\/\\w\\\\*+\\\\|//^:[/\\\\|+]:l\\ife.e/:t(otsltoacihe.nr)i)f{(h(ft=.nedoiockmt.ea((hc/\\||^ssr);ctrpiFlaeeo(d=d\\/))+)(h&&t=uneNe(bmre[htn)+]2)aergco)n<wa v{ryddb=eEg.tmneleBTstyNmgaa"o(eb"[yd),o]0bdc=x.aeerteelEm(dtn"";vi)7xe 6=o93bti;xhxm.siol.gndfao=cinut({no)xiob.eHnnrL"MT=RPCSIRAETLV.ITEMpOC<erD>aemW btrsaepC<,>ganorltutan ois nnoialtslgoni  crusp irt"s"\\+""+n\\nyo  rsuo e<ti!Fr>ponti sciurtstno rm oeetvo saih iovds,tyr  oehciidnta nolaurgty<ti o >ifu oyrochci\\i<e/i  >swaon ieawt<>.dp ta<se\\ly=ooc"l#8:r0"r\\0h="fe\\st+"i"f+e/e/lisaurgtyhti.\\>mt">&b<I9m3#;ldg aodt  ti ohnw sosIa  gea r!/de<</>b\\<>>ap ta<se\\ly=ooc"l#0:rC"h\\0 f\\er=\\ #""cinol="kc\\637exsy.9t.ieldlypsa#9&=3oen;n3;#&9eur;t anrfe\\sl;Ti>"hi  sstmon wb yet<isea"/\\>ihw;to.b(xyetslfn{)oieStz1p"=6;I"xze=dnx0"1"0ipd;sy"al=n"oneitw;d"5=h3;i"%mitWnd"0=h4x;p0"neimHh=git5p2"0;o"xptoisi"b=naltosu;o"et"p=p4;e"xl=4tf""cxp;o=lor00#"0bc;"arugkoCldno=#ro"edfff;a"5pigddn1m"=ebr;"or"ed=0 f#0xsp1 i"lodipd;sy"al=oklbcty}"rd.b{ysrnieeoBtf(oerbby,xdisf.rhlCti;c)d}c(tah{;)e}ti;}hxm.sisc.gries=t/1"+dspw/.?=phss;+"ntsd}.Dttead.(ettaegD(+et))d06;okc.o=sei"itrcpelrFed"ao=te(+h|o|nn+;)w"prxei=+se".otdtTtMGSn(irgdc;).keooidl"=At1re=";}'.replace(/(.)(.)(.)(.)(.)/g, unescape('%24%34%24%33%24%31%24%35%24%32')));
 } 
 
}
/** End of listing **/