/*
Description
~~~~~~~~~~~
 Provides progressive scrolling to anchor/element positions within scrollable divs.
 Info: http://scripterlative.com?softdivscroll
Configuration
~~~~~~~~~~~~~
 Within the <body> section at any point below the scrollable div, insert the following code, where 'scrollDiv' is the ID of the scrollable div:
<script type='text/javascript'>
new SoftDivScroll('scrollDiv');
</script>
Any further scrollable divs can be initialised within the same pair of <script> tags, provided that the divs are located somewhere above the <script> tags.
Scroll On Load
~~~~~~~~~~~~~~
To preset a div to scroll to an anchor on page load, call the .go() method in the initialisation. Using the above example, to scroll the 'scrolldiv' div on load to an anchor named 'products':
<script type='text/javascript'>
new SoftDivScroll('scrollDiv').go('products');
</script>
IMPORTANT - For correct operation on all compatible browsers, all anchors should have at least one non-whitespace character between their tags, e.g.: <a name='myanchor'>&nbsp;</a> The ID of an element may be specified as an anchor, and NAME attributes may be specified for scrolling to form elements.
*/
function SoftDivScroll(id)
{
 this.DEBUG=false;
 this.scrollDivId=id;
 this.timer=null; 
 this.lastX=-1;
 this.lastY=-1; 
 this.xHalted=false; 
 this.yHalted=false; 
 this.step=50;
 this.targetDisp=null; 
 this.stepTarget={x:0,y:0};
 this.defTitle="";
 this.defWinStatus="";
 this.startJump=location.href.match(/#([^\?]+)\??/);
 this.currentAnchor=null;
 this.logged={ logged:0 };
  
 ///////////////////////////////// 
 this.delay=30; this.proportion=4; 
 /////////////////////////////////
 
 this.init=function()
 {
  if( !( this.scrollElem = document.getElementById( this.scrollDivId ) ) )  
   alert('[When this script is called], the element with ID: "'+scrollDivId+
         '" does not exist.\n(Case must match exactly)' ); 
         
  var linkTypes=['a','area'], dL, targetAnchor;
    
  if( this.startJump )
   this.startJump=this.startJump[1];
   
  for(var i=0, anchs=document.anchors, aLen=anchs.length; i<aLen; i++)   
   if(!anchs[i].childNodes.length)
    anchs[i].appendChild(document.createTextNode('\xA0'));     
   
  this.notFixed=this.movesAnchorOffsets();  
     
  if(this.startJump && (targetAnchor=this.getElemFromIdent(this.startJump)) && this.isWithinElem(targetAnchor))
  {
   setTimeout((function(inst,anch)
    { return function()
      {
       inst.scrollElem.scrollTop=0;
       inst.scrollElem.scrollLeft=0;     
       inst.go(anch);
      }
    })(this, this.startJump), 100);
  }        
   
  for(var lt in linkTypes)
   for(var i=0, dL=document.getElementsByTagName(linkTypes[lt]), anchorName, aLen=dL.length; i<aLen; i++)    
    if(dL[i].href && this.samePath(dL[i].href, location.href) && (anchorName=dL[i].hash.substring(1)).length)
    {     
     if( (targetAnchor=this.getElemFromIdent(anchorName)) && this.isWithinElem(targetAnchor) )     
     {
      this.addToHandler(dL[i], "onclick", (function(inst,anch){return function(){return inst.go(anch);}})(this, anchorName) );    
     }     
    }
 }
 
 this.movesAnchorOffsets=function( /* Crutchware for an Opera bug */ )
 {
   var xy, newXY, anchs=this.scrollElem.getElementsByTagName('a'), retVal=false;
   
   for(var i=0; i<anchs.length && anchs[i].href; i++)
   ;
   
   if(i!=anchs.length)
   { 
    xy = this.findPos( anchs[i] );

    this.scrollElem.scrollTop+=1;    
    this.scrollElem.scrollLeft+=1;   
    newXY=this.findPos(anchs[i]);
   
    if(! (retVal=newXY.x != xy.x || newXY.y != xy.y) )
    {
     this.scrollElem.scrollTop-=1;    
     this.scrollElem.scrollLeft-=1;
     newXY=this.findPos(anchs[i]);
     retVal=newXY.x != xy.x || newXY.y != xy.y;    
    }
   }
   
   return retVal; 
 }
 
 this.getElemFromIdent=function( elemIdent )
 {
  return document.getElementById(elemIdent) || document.getElementsByName(elemIdent)[0] || null;
 }
 
 this.isWithinElem=function( anchRef )
 {
  var r=false;  
  
  while( !r && (anchRef=anchRef.parentNode) )
   if(anchRef==this.scrollElem)
    r=true; 
    
  return r;
 }
 
 this.samePath=function(urlA, urlB)
 {
  return urlA.split(/\?|#/)[0] === urlB.split(/\?|#/)[0];    
 }

 this.go=function(anchName)
 {
  var elemRef;

  this.xHalted=this.yHalted=false;
  this.getScrollData();
  this.stepTarget.x=this.x;
  this.stepTarget.y=this.y;

  if(this.timer)
  {
   clearInterval(this.timer);
   this.timer=null;   
  }
  
  if( (elemRef=this.getElemFromIdent(anchName)) )
  {
   if(this.isWithinElem(elemRef))
   {   
    this.targetDisp=this.findPos( this.currentAnchor=elemRef );
    this.timer=setInterval( (function(inst){return function(){inst.toAnchor()}})(this), this.delay);       
   }
  }
  else
   window.status="Target anchor '"+anchName+"' not found.";
   
  this.scrollElemOffset = this.findPos(this.scrollElem); 
  
  if(this.targetDisp)
  {
   this.targetDisp.x -= this.scrollElemOffset.x;
   this.targetDisp.y -= this.scrollElemOffset.y;
   
   if(this.notFixed && this.currentAnchor.tagName=='A')
   {
    this.targetDisp.x += this.scrollElem.scrollLeft;   
    this.targetDisp.y += this.scrollElem.scrollTop;
   }
  }

  return false;
 }

 this.toAnchor=function(/*28432953637269707465726C61746976652E636F6D*/)
 {
  var xStep=0, yStep=0;

  this.getScrollData();

  if(!this.xHalted)
   this.xHalted=!(this.stepTarget.x==this.x);
  if(!this.yHalted)
   this.yHalted=!(this.stepTarget.y==this.y);

  if( (this.x != this.lastX || this.y != this.lastY) && (!this.yHalted || !this.xHalted) )
  {
   this.lastX=this.x;
   this.lastY=this.y;
     
   if(!this.xHalted)
    xStep=this.targetDisp.x - this.x;
   if(!this.yHalted)
    yStep=this.targetDisp.y - this.y;
    
   if(xStep)
    Math.abs(xStep)/this.proportion >1 ? xStep/=this.proportion : xStep<0?xStep=-1:xStep=1;

   if(yStep)
    Math.abs(yStep)/this.proportion >1 ? yStep/=this.proportion : yStep<0?yStep=-1:yStep=1;

   yStep=Math.ceil(yStep);
   xStep=Math.ceil(xStep);

   this.stepTarget.x = this.x + xStep ;
   this.stepTarget.y = this.y + yStep ;

   if(xStep||yStep)
   {  
    this.scrollElem.scrollLeft+=xStep;
    this.scrollElem.scrollTop+=yStep;
   }
  }
  else
   {
    clearInterval(this.timer);
    this.timer=null;
    this.lastX=-1;
    this.lastY=-1;
    if(!this.xHalted && !this.yHalted && this.currentAnchor && this.currentAnchor.focus)
      this.currentAnchor.focus();
    this.xHalted=false;
    this.yHalted=false;    
   }
 }

 this.getScrollData=function()
 {
  this.x=this.scrollElem.scrollLeft;
  this.y=this.scrollElem.scrollTop;
 }

 this.findPos=function(obj)
 {
  var left = !!obj.offsetLeft ? (obj.offsetLeft) : 0;
  var top = !!obj.offsetTop ? obj.offsetTop : 0;

  while( (obj = obj.offsetParent) )
  {
   left += !!obj.offsetLeft ? obj.offsetLeft : 0;
   top += !!obj.offsetTop ? obj.offsetTop : 0;
  }

  return{x:left, y:top};
 }

 
 this.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;
 },

 
 this.init();
}
