/*
 balloon.js -- a DHTML library for balloon tooltips

 Sheldon McKay <mckays@cshl.edu>
 $Id: balloon.js,v 1.5 2007/04/02 19:16:43 smckay Exp $

 See http://www.wormbase.org/wiki/index.php/Balloon_Tooltips
 for documentation.
*/

/////////////////////////////////////////////////////////////////////
// Configuration -- Edit as required
/////////////////////////////////////////////////////////////////////

// Balloon connector (the triangle part) images
// Default images are provided, they are 58 px in height
var upLeftConnector  = 'http://www.e-vitamines.com/js/images/balloon_up_bottom_left.png';
var upRightConnector = 'http://www.e-vitamines.com/js/images/balloon_up_bottom_right.png';
var downLeftConnector  = 'http://www.e-vitamines.com/js/images/balloon_down_top_left.png';
var downRightConnector = 'http://www.e-vitamines.com/js/images/balloon_down_top_right.png';

// Balloon body background images
// NOTE: the balloon body height is exaggerated to allow for a sliding
// non-repeat background image for an arbirtrary amount of text.  The
// height of the default balloon images is 900px, but only a small part
// is usually visible, depending on the content dimensions. 
var upBalloon   = 'http://www.e-vitamines.com/js/images/balloon_up_top.png';
var downBalloon = 'http://www.e-vitamines.com/js/images/balloon_down_bottom.png';

// Balloon dimensions and text placement
// the default width 300px images (8 pixel shadow)
var balloonWidth   = '308px';
var paddingTop     = '20px';
var paddingLeft    = '15px';
var paddingRight   = '15px';
var paddingBottom  = '20px';
var paddingConnector = '48px';

// Horizontal offset: allowed values are 'left' and 'right'
// the offset will be flipped as required to keep the balloon onscreen
hOffset = 'left';

// Location of optional ajax handler that returns tooltip contents
var helpUrl = null;
//var helpUrl = '/db/misc/help';

// Default tooltip text size
var balloonTextSize = '90%';

// Delay (milliseconds) before balloon is displayed
// Don't set it too low or you may annoy your users!
var delayTime = 750;


/////////////////////////////////////////////////////////////////////
// Best not to edit below this point   
/////////////////////////////////////////////////////////////////////

// various active layers
var activeEl,activeBalloon,activeBody,activeText;

// active parameters
var timeoutTooltip,currentHelpText,balloonIsStatic;
var balloonIsVisible,vOffset='5px',activeUrl;
var activeTop,activeBottom,activeLeft,activeRight;

// document.shortcut
var D=document;

// is this IE or opera?
var isIE = D.all && !window.opera;
var isOpera = window.opera;


/////////////////////////////////////////////////////////////////////////
// This is the function that is called on mouseover.  It has a built-in
// delay time to avoid balloons popping up on rapid mousover events
/////////////////////////////////////////////////////////////////////////

function showTooltip(evt,caption,sticky) {
  if(balloonIsVisible) return false;
  var el = getEventTarget(evt);
  
  // attach a mousout event to the target element
  el.onmouseout = hideTooltip;

  // set the active coordinates
  setActiveCoordinates(evt);

  // Opera tooltip workaround
  if (isOpera && (el.getAttribute('title') || el.getAttribute('href')) ) 
    sticky = true;
 
  balloonIsStatic ? hideStaticTooltip() : hideTooltip();  
  balloonIsStatic = sticky;

  timeoutTooltip = window.setTimeout(doShowTooltip,delayTime);

  currentHelpText = caption;
}

// abort delayed deployment
function timeout() {
  window.clearTimeout(timeoutTooltip);
}


/////////////////////////////////////////////////////////////////////
// Tooltip rendering function
/////////////////////////////////////////////////////////////////////

function doShowTooltip() {
  // Stop firing if a balloon is already being displayed
  if (balloonIsVisible) return false;  

  // make sure user-configured numbers are not strings
  parseIntAll();

  // actual window dimensions
  var pageWidth  = YAHOO.util.Dom.getViewportWidth();
  var pageHeight = YAHOO.util.Dom.getViewportHeight();
  var pageTop    = isIE ? D.body.scrollTop : window.pageYOffset;
  var pageMid    = pageTop + pageHeight/2;
  var pageBottom = pageTop + pageHeight;

  // balloon placement tied to onmouseover element
  var left,hOrient;
  if (activeLeft < balloonWidth) {
    hOrient = 'right';
    left = activeRight;
  }
  else if ((activeRight + balloonWidth) > pageWidth) {
    hOrient = 'left';
    left = activeLeft - balloonWidth;
  }
  else {
    hOrient = hOffset;
    left = hOrient == 'left' ? (activeLeft - balloonWidth) : activeRight;
  }

  // balloon is up if below midline, down otherwise
  var top,vOrient;
  if (activeTop > pageMid) {
    vOrient = 'up';
    top = activeTop - Math.abs(vOffset);
  }
  else {
    vOrient = 'down';
    top = activeBottom + Math.abs(vOffset);
  }

  // Get or create the balloon layer.
  activeBalloon = D.getElementById(vOrient) || createAndAppend(vOrient);
  ySetStyle(activeBalloon,'display','none');
  ySetStyle(activeBalloon,'position','absolute');

  // look for url 
  if (currentHelpText.match(/^url:/i)) {
    var urlArray = currentHelpText.split(':');
    activeUrl = urlArray[1];
  }

  // request the contents synchronously (ie wait for result)
  var helpText = getContents(currentHelpText);

  // configure for up or down orientation
  if (vOrient == 'up') {
    var upConnector = hOrient == 'left' ?  upLeftConnector : upRightConnector; 
    ySetStyle(activeBalloon,'background','url('+upConnector+') bottom left no-repeat');
    ySetStyle(activeBalloon,'padding-bottom',paddingConnector);
    ySetStyle(activeBalloon,'padding-top',paddingTop);
    var upCaption = D.getElementById('upCaption') || createAndAppend('upCaption',activeBalloon);
    var upText    = D.getElementById('upText')    || createAndAppend('upText',upCaption);
    ySetStyle(upCaption,'background','url('+upBalloon+') top left no-repeat');
    ySetStyle(upCaption,'padding-top',paddingTop);    
    ySetStyle(upCaption,'padding-bottom',1);
    activeBody = upCaption;
    activeText    = upText;
  }
  else {
    var downConnector = hOrient == 'left' ?  downLeftConnector : downRightConnector;
    ySetStyle(activeBalloon,'background','url('+downConnector+') top left no-repeat');
    ySetStyle(activeBalloon,'padding-bottom',paddingBottom);
    ySetStyle(activeBalloon,'padding-top',paddingConnector);
    var downCaption = D.getElementById('downCaption') || createAndAppend('downCaption',activeBalloon);
    var downText    = D.getElementById('downText')    || createAndAppend('downText',downCaption);
    ySetStyle(downCaption,'background','url('+downBalloon+') bottom left no-repeat');
    ySetStyle(downCaption,'padding-top',1);
    ySetStyle(downCaption,'padding-bottom',paddingBottom);
    activeBody = downCaption;
    activeText    = downText;
  }
  
  // text boundaries
  ySetStyle(activeBody,'padding-left',paddingLeft);
  ySetStyle(activeBody,'width',balloonWidth);
  ySetStyle(activeBody,'z-index',10000);
  ySetStyle(activeText,'width',balloonWidth - (paddingLeft + paddingRight));
  ySetStyle(activeText,'text-size',balloonTextSize);


  // persistent balloons need a close control
  if (balloonIsStatic) {
    if (vOrient == 'up') {
      ySetStyle(activeBody,'padding-top',7);
    }
    else {
      var margin = isIE ? -4 : -8;
      ySetStyle(activeText,'margin-top',margin);
      ySetStyle(activeBody,'padding-top',1);
    }

    helpText = '\
    <a onClick="hideStaticTooltip()" title="close this balloon" href=#\
    style="float:right;text-size:8px;text-decoration:none;text-color:40BB21;">\
    <FONT SIZE=1 COLOR=40BB21 FACE=Tahoma><b><u>FERMER [X]</u></b></FONT></a><br>' + helpText;
  }
  else {
    if (vOrient == 'up') {
      ySetStyle(activeBody,'padding-top',paddingTop);
    }
    else { 
      ySetStyle(activeBody,'padding-top',1);
      ySetStyle(activeText,'margin-top',0);
    }
  }


  // add the text to the caption layer
  activeText.innerHTML = helpText;

  showBalloon(vOrient,left,top,pageTop,pageBottom);
}

/////////////////////////////////////////////////////////////////////
// Convenience functions
/////////////////////////////////////////////////////////////////////

// Set the active mouseover coordinates
function setActiveCoordinates(evt) {
  var el = getEventTarget(evt);
  var XY = eventXY(evt);
  // prefer element vertical bounds if available
  // otherwise, use event's
  activeTop  = yGetLoc('y1') || XY[1];
  activeTop -= 10;

  
  activeLeft = XY[0] - 10;

  activeRight = activeLeft + 20;
  activeBottom = yGetLoc('y2');
  if (activeBottom) activeBottom += 10;
  else activeBottom = activeTop + 20;
}

// Function based on example by Peter-Paul Koch
// http://www.quirksmode.org/js/events_properties.html
function eventXY(e) {
  var posx = 0;
  var posy = 0;
  if (!e) var e = window.event;
  if (e.pageX || e.pageY)       {
    posx = e.pageX;
    posy = e.pageY;
  }
  else if (e.clientX || e.clientY)      {
    posx = e.clientX + document.body.scrollLeft
                     + document.documentElement.scrollLeft;
    posy = e.clientY + document.body.scrollTop
                     + document.documentElement.scrollTop;
  }

  var XY = new Array(2);
  XY[0] = posx;
  XY[1] = posy;
  return XY;
}

// Function based on example by Peter-Paul Koch
// http://www.quirksmode.org/js/events_properties.html
function getEventTarget(e) {
  var targ;
  if (!e) var e = window.event;
  if (e.target) targ = e.target;
  else if (e.srcElement) targ = e.srcElement;
  if (targ.nodeType == 3) targ = targ.parentNode; // Safari
  return targ;
}

function ySetStyle(el,att,val) {
  if (att.match(/left|top|width|height|padding|margin/)) val += 'px'; 
  if (el) YAHOO.util.Dom.setStyle(el,att,val);
}

function yGetLoc(el,request) {
  var region = YAHOO.util.Dom.getRegion(el);
  switch(request) {
    case ('y1') : return region.top;
    case ('y2') : return region.bottom;
    case ('x1') : return region.left;
    case ('x2') : return region.right;
    case ('width')  : return (region.right - region.left);
    case ('height') : return (region.bottom - region.top);
    case ('region') : return region; 
 }
}

// We don't know if numbers are overridden with strings
function parseIntAll () {
  balloonWidth     = parseInt(balloonWidth);
  paddingTop       = parseInt(paddingTop);
  paddingLeft      = parseInt(paddingLeft);
  paddingRight     = parseInt(paddingRight);
  paddingBottom    = parseInt(paddingBottom);
  paddingConnector = parseInt(paddingConnector);
  vOffset          = parseInt(vOffset);
}

/////////////////////////////////////////////////////////////////////
// Create/append  balloon elements
/////////////////////////////////////////////////////////////////////

function createAndAppend(id,parent,elTag) {
  var node = justCreate(id,elTag);
  justAppend(node,parent);
  return node;
}

function justCreate(id,elTag) {
  var tag = elTag || 'div';
  var node = D.createElement(tag);
  node.setAttribute('id', id);  
  return node;
}

function justAppend(child,parent) {
  var parentNode = parent || D.body;
  parentNode.appendChild(child);
}


/////////////////////////////////////////////////////////////////////
// Balloon visibility controls
/////////////////////////////////////////////////////////////////////

function showBalloon(orient,left,top)  {
  YAHOO.util.Dom.setY(activeBalloon,999999999);
  ySetStyle(activeBalloon,'display','inline');


  if (orient == 'up') {
    var height = yGetLoc(activeBalloon,'height');
    top -= height;
  }

  YAHOO.util.Dom.setY(activeBalloon,top);
  YAHOO.util.Dom.setX(activeBalloon,left);
  balloonIsVisible = true;
  showHideSelect();
}

function hideTooltip() {
  timeout();
  if (balloonIsStatic) return false;
  balloonIsVisible = false;
  if (activeBalloon) {
    showHideSelect(1);
    ySetStyle(activeBalloon,'display','none');
  }
}

function hideStaticTooltip() {
  balloonIsVisible = false;
  if (activeBalloon) {
    showHideSelect(1);
    ySetStyle(activeBalloon,'display','none');
  }
}

// to be called externally
function hideAllTooltips() {
  timeout();
  if (activeBalloon) ySetStyle(activeBalloon,'display','none');
  balloonIsVisible = false;
}

// IE select z-index bug
function showHideSelect(visible) {
  if (!isIE) return false;
  var sel = D.getElementsByTagName('select');
  if (!sel) return false;
  visible = visible ? 'visible' : 'hidden';
  for (var i=0; i<sel.length; i++) {
    if (isOverlap(sel[i]))
      ySetStyle(sel[i],'visibility',visible);
  }
}

function isOverlap(sel) {
  if (!activeBalloon) return false;
  var R1 = yGetLoc(activeBalloon,'region');
  var R2 = yGetLoc(sel,'region');
  var t1=R1.top,b1=R1.bottom,l1=R1.left,r1=R1.right;
  var t2=R2.top,b2=R2.bottom,l2=R2.left,r2=R2.right;

  if ( ((t2 < b1) && (t2 > t1)) 
      && (((l2 > l1) && (l2 < r1)) || (r2 < r1) && (r2 > l1))) return true;

  if ( ((b2 < b1) && (b2 > t1))
      && (((l2 > l1) && (l2 < r1)) || (r2 < r1) && (r2 > l1))) return true;

  return false;
}


///////////////////////////////////////////////////////
// AJAX widget to fill the balloons
// requires prototype.js
///////////////////////////////////////////////////////
function getContents (section) {
  // just pass it back if no AJAX handler is required.
  if (!helpUrl && !activeUrl) return section;
  
  // inline URL takes precedence
  var url = activeUrl || helpUrl;

  var pars = activeUrl ? '' : 'section='+section+';';

  var ajax  = new Ajax.Request( url,
                           { method:   'get',
                             asynchronous: false,
                             parameters:  pars,
                             onSuccess: function(t) { updateResult(t.responseText) },
                             onFailure: function(t) { alert('AJAX Failure! '+t.statusText)}});

  // activeUrl is meant to be single-use only
  activeUrl = null;

  return helpText || section;
}

function updateResult (text) {
  helpText = text;
}
