// effects v0.1, 
// Copyright (c) 2003 Bogdan Ionescu (bogdan@febee.ro)
// This script is distributed under the terms of the GPL (gnu.org)




//Tool class provides some cross browser functions

function Tool() {  
  this.document = document; 
  this.browserType; //0 IE, 1-netscape/mozilla 2-opera
  this.quirk_mode = true;
  this.getCompatMode = function(){     
    
    this.browserType = (navigator.userAgent.indexOf('Gecko') != -1) +
    2*(navigator.userAgent.indexOf('Opera') != -1);  
    
    //assuming IE if its not opera or netscape
    this.quirk_mode = (!this.document.compatMode || this.document.compatMode == "BackCompat" || 
                       this.document.compatMode == "QuirksMode");             
  }

  this.getCompatMode();
}

Tool.prototype.findDocument = function(obj) {  
  if(!this.isNode(obj)) return this.getDocument();
  while(obj.nodeName != '#document') {
    obj = obj.parentNode;
  }
  return obj;
}

Tool.prototype.getDocument = function() {
  return this.document;  
}

Tool.prototype.isNode = function(obj) {
  return (obj.nodeType != null);
}

Tool.prototype.getElement = function( obj ) {
  if(this.isNode(obj)) return obj;
  else return this.getDocument().getElementById( obj );
}

Tool.prototype.getStyle = function(id){  
  var obj = this.getElement(id);
  var doc = this.findDocument(obj);

  if(obj.currentStyle) {
    return obj.currentStyle; //IE
  }
  
  if( doc.defaultView && doc.defaultView.getComputedStyle) {
    return doc.defaultView.getComputedStyle(obj, ''); //Mozilla
  }
  
  return obj.style; //Opera. Quite sadly styles defined in stylesheets wont work;
}

Tool.prototype.getAbsoluteLeft = function(id) {
  return this.getAbsolutePosition(id)[0];
}

Tool.prototype.getAbsoluteTop = function(id) {
  return this.getAbsolutePosition(id)[1];
}

Tool.prototype.getAbsolutePosition = function (id) {
  var x = y = 0;

  /*
   * Workaround for IE. Some very strange bug occurs when the first child of a node
   * has styleFloat/cssFloat set to 'right'. The offsetLeft of the Node somehow is
   * set to the offsetLeft of its child, therefore here comes the workaround
   */

  //<start IE_workaround>
  if(!this.browserType && id.childNodes.length) {       
      
    var firstElement = id.firstChild;
    if(firstElement.currentStyle != null && firstElement.currentStyle.styleFloat == 'right'){      
      firstElement.style.styleFloat="none";
      x += id.offsetLeft;
      y += id.offsetTop;
      firstElement.style.styleFloat="right";      
      id=id.offsetParent;
    }
  }
  // </end IE_workaround>
    
  while(id){
    x += id.offsetLeft;
    y += id.offsetTop;
    id = id.offsetParent;

    if( id ) {
      var st=this.getStyle(id);
      x+= this.Int(st.borderLeftWidth);
      y+= this.Int(st.borderTopWidth);        
    }
      
  }    
  return new Array(x,y)
};

Tool.prototype.setInnerHeight = function (element, sH) {
  if (this.browserType == 1 || !this.quirk_mode ) {      
  element.style.height = this.Px(sH);    
  }
  else {    
    element.style.height = this.Px(sH + element.offsetHeight - this.getInnerHeight(element));
  }
}

Tool.prototype.setInnerWidth = function (element, sW) {
  if (this.browserType == 1 || !this.quirk_mode ) {
    element.style.width = this.Px(sW);        
  }
  else {        
    element.style.width = this.Px(sW + element.offsetWidth - this.getInnerWidth(element));    
  }
}

Tool.prototype.getInnerHeight = function (element) {    
  var st = this.getStyle(element);
  var dh = this.Int(st.borderTopWidth) + this.Int(st.borderBottomWidth) + 
  this.Int(st.paddingTop) + this.Int(st.paddingBottom);        
  return element.offsetHeight - dh;  
}

Tool.prototype.getInnerWidth = function (element) {
  var st = this.getStyle(element);
  var dw = this.Int(st.borderLeftWidth) + this.Int(st.borderRightWidth) +  
  this.Int(st.paddingLeft) + this.Int(st.paddingRight);        
  return element.offsetWidth - dw;  
}


Tool.prototype.getWindowHeight = function() {
  if(this.browserType || this.quirk_mode) {    
    return document.body.clientHeight;
  }
  else {//workaround for IE
    return document.documentElement.clientHeight;
  }
}

Tool.prototype.getWindowWidth = function() {  
  return document.body.clientWidth;
}

Tool.prototype.Int = function(src){
  var val = parseInt(src);
  if(isNaN(val)) return 0;
  else return val;
}

Tool.prototype.Px = function(val){
  return val + 'px';
}

Tool.prototype.addListener = function(object, evStr, handler) {
  var obj = this.getElement(object);
  if(evStr.indexOf('on') == 0) evStr = evStr.substr(2, evStr.length - 2);

  if(obj.attachEvent)
     return obj.attachEvent('on' + evStr, handler);

  if(obj.addEventListener) {   
     return obj.addEventListener(evStr, handler, true);       
  }

  eval(obj + '.on' + evStr + '=' + handler);
  
}



/*
 * displayEffect is the function that offers access to the current defined
 * effects. You can expand it easily. The parameters are explained below
 */

displayEffect = function (id, timeout, effect, subeffect, useClone, onStop) {  
  var obj = (new Tool()).getElement(id);           

  this.middle = middleEffect;  
  this.bring = moveEffect;
  this.margin = marginEffect;
  
  var effects = new Array(this.middle, this.bring, this.margin);
  var type = effect % effects.length;    
  new effects[type]().go(obj,timeout, subeffect, useClone, onStop);   
}


/***********************************************
 *            effects functions                *
 ***********************************************/

/*
 * effectBase is the base class of all defined effects
 * It provides some predefined functionality so that new effects can be added
 * without much trouble.
 *
 * Everything starts with the go(obj, timeout, effect, clone, onStop) method.
 * obj - the target object
 * timeout - the elapsed time for the demo
 * effect - the subtype of the effect 
 * clone - if true, a clone of obj is created so the layout of the page wont
 *         suffer while the effect takes place
 * onStop - if set, it must be a function. It will be called when the effect
 *          ends.
 */


effectBase = function() {
  var dis = this;       
  this.Getter = new Tool();
  this.init = function(obj) {
    return true;
  }

  this.savecssText = function() {    
    var bst = this.obj.style;
    this.cssText= this.Getter.browserType < 2 ?bst.cssText:
    new Array(bst.left, bst.top, bst.width, bst.height, bst.margin, bst.cursor, bst.position);  
  }

  this.restorecssText = function() {    
    if(this. Getter.browserType < 2){      
      this.obj.style.cssText = this.cssText;
    }
    else{
      //<Opera7 workaround>
      //for some reason Opera doesn't support cssText as the other 2 browsers do
      this.obj.style.left = this.cssText[0];
      this.obj.style.top = this.cssText[1];
      this.obj.style.width = this.cssText[2];
      this.obj.style.height = this.cssText[3];
      this.obj.style.margin = this.cssText[4];
      this.obj.style.cursor = this.cssText[5];
      this.obj.style.position = this.cssText[6];      
    }
  }
              
  
  this.baseInit = function(obj, timeout, effect, clone, onStop) {        
    if(obj.effectInProgess) return -1;
    obj.effectInProgess = true;          
    this.onStop = onStop;
    this.effect = effect;      
    this.obj = obj;
    this.clone = obj;
    this.counter = 0;      
    this.steps = 25;
    this.delay=timeout/this.steps;      
    this.savecssText();    
    if(clone) this.initClone();    
    this.Getter.setInnerHeight(this.clone, this.Getter.getInnerHeight(this.obj));    
    this.Getter.setInnerWidth(this.clone, this.Getter.getInnerWidth(this.obj));     
    this.clone.style.display = 'block';      
    this.clone.style.overflow = 'hidden';        
      
    return this.init(obj);
  }    

  this.initClone = function() {    
    this.cloned = true;
    this.visibility = this.obj.style.visibility;
    this.clone = this.obj.cloneNode(true);            
    this.obj.style.visibility = 'hidden';
      
    this.clone.style.position = 'absolute';          
    this.clone.style.left = this.Getter.Px(this.Getter.getAbsoluteLeft(this.obj));
    this.clone.style.top = this.Getter.Px(this.Getter.getAbsoluteTop(this.obj));      
    document.body.appendChild(this.clone);                      
  }

  this.removeClone = function() {     
    document.body.removeChild(this.clone);
    this.obj.style.visibility=this.visibility;
  }

  this.restore = function() {
    this.obj.effectInProgess = null;
    this.restorecssText();
    if(this.cloned)this.removeClone();              
    if(this.onStop) {
      this.onStop();      
    }
    
  }

  this.display = function () {
    //this function will be overriden in the derived classes      
    window.status='Step ' + this.counter + 1;
  }

  this.nextStep = function() {
    if(this.counter == this.steps) {        
      clearInterval(this.interval);
      this.interval = null;
      this.restore();        
      return;
    }
    else {             
      if(!this.counter) this.clone.style.visibility = 'visible';
      this.display();
      this.counter++;           
    }      
  }

  this.go = function(obj, timeout, effect, clone, onStop){
    if(this.baseInit(obj, timeout, effect, clone, onStop) != -1)
    this.interval = setInterval(function(){dis.nextStep();}, this.delay);
  }
    
}




/*
 * here come the defined effects
 */
var marginEffect = function (){
    /* values of this.effect: 
     * from 0 to 5 the object expands.
     * from 10 to 15 the object shrinks.
     * 0/10 - from top/left to right/bottom
     * 1/11 - left to right, 
     * 2/12 - top to bottom, 
     * 3/13 - right to left, 
     * 4/14 - bottom to top, 
     * 5/15 - right/bottom to top/left
     */
    if(effectBase.call) 
    effectBase.call(this);            
    this.prototype = effectBase.prototype;
    
    this.init = function(obj) {                      
      this.dX = this.Getter.getInnerWidth(this.obj)/this.steps;        
      this.dY = this.Getter.getInnerHeight(this.obj)/this.steps;            
      this.dX0 = this.clone.offsetLeft;
      this.dY0 = this.clone.offsetTop;
      this.shrink = this.effect >= 10;
      if(this.shrink) this.effect%= 10;
    }

    this.display = function() {                     
      if(!(this.effect % 5) || (this.effect % 2) ) {
        this.Getter.setInnerWidth(this.clone, this.dX * 
                             Math.abs(this.shrink*this.steps-this.counter));
        if(this.effect > 1) {
          this.clone.style.left = this.Getter.Px(this.dX0 + this.dX * 
                             Math.abs((1-this.shrink)*this.steps - this.counter));
        }
      }
      if(!(this.effect % 5) || !(this.effect % 2)) {        
        this.Getter.setInnerHeight(this.clone, this.dY * 
                             Math.abs(this.shrink*this.steps -this.counter));    
        if(this.effect > 2) {
          this.clone.style.top = this.Getter.Px(this.dY0 + this.dY *  
                             Math.abs((1-this.shrink)*this.steps -this.counter));
        }
      }    
    }
  }  

var middleEffect = function () {
    /* values of this.effect:
     * from 0 to 3 it expands the object
     * from 10 to 13 it shrinks the object
     * effect: 0/10 - both direction, 1/11 - horizontal, 2/12 - vertical
     */
    if(effectBase.call) 
    effectBase.call(this);    
    this.init = function(obj) {      
      
      this.dX = this.Getter.getInnerWidth(this.obj) / (2 * this.steps);
      this.dY = this.Getter.getInnerHeight(this.obj) / (2 * this.steps);      
      this.dX0 = this.clone.offsetLeft;
      this.dY0 = this.clone.offsetTop;
      this.shrink = this.effect >= 10;
      if(this.shrink) this.effect%= 10;
    }

    this.display = function() {            
      if(!this.effect  || this.effect == 1) {
        this.Getter.setInnerWidth(this.clone, 2 * this.dX * Math.abs(this.shrink * this.steps - this.counter));                
        this.clone.style.left = this.Getter.Px(this.dX0 + Math.abs((1-this.shrink) * this.steps - this.counter) * this.dX);
      }
      if(!this.effect || this.effect == 2) {        
        this.Getter.setInnerHeight(this.clone, 2*this.dY * Math.abs(this.shrink * this.steps - this.counter));
        this.clone.style.top = this.Getter.Px(this.dY0 + Math.abs((1-this.shrink) * this.steps - this.counter) * this.dY);      
      }
    }
  }


var moveEffect = function() {
    /*
     * Values of this.effect:
     * from 0 to 7 -moving the object from outside to inside
     * from 10 to 17 - moving the object from inside to outside
     * 0/10 - from/to left, 
     * 1/11 - from/to (left/top)
     * 2/12 - from/to (left/bottom)
     * 3/13 - from/to bottom
     * 4/14 - from/to top
     * 5/15 - from/to right
     * 6/16 - from/to (right/bottom)
     * 7/17 - from/to (right/top)
     */

    if(effectBase.call) 
    effectBase.call(this);

    this.init = function(obj) {      
      this.moveout = this.effect >= 10;      
      if(this.moveout) this.effect%= 10;
      // moveLeft 0-from left, 1-from right , 2 -unchanged     
      var moveLeft = this.effect < 3? 0 : (this.effect > 4 ? 1 : 2);
      // moTop 0-unchanged, 1-from top, 2-from bottom
      var moveTop = (this.effect % 3 == 1)? 0: (this.effect % 5 ? 1 : 2);      
      switch(moveLeft) {
      case 0: 

        this.dX = (this.Getter.getAbsoluteLeft(obj) + this.clone.offsetWidth)/this.steps *(this.moveout?-1:1);
        this.dX0 = this.moveout * this.Getter.getAbsoluteLeft(obj) + (this.moveout - 1) * this.clone.offsetWidth;        
        
        break;
      case 1: 
        this.dX = (this.Getter.getAbsoluteLeft(this.clone) + 1.1 * this.obj.offsetWidth
                   - this.Getter.getWindowWidth()) / this.steps * (this.moveout?-1:1);
        

        if(this.moveout)        
          this.dX0 = this.Getter.getAbsoluteLeft(this.clone);        
        else                     
          this.dX0 = this.Getter.getWindowWidth() - 1.1 * this.obj.offsetWidth;
          
        break;
      case 2:
        this.dX = 0;
        this.dX0 = this.Getter.getAbsoluteLeft(this.obj);         
        break;
      }
      
      switch(moveTop) {
        case 0:
        this.dY = (this.Getter.getAbsoluteTop(obj) + this.clone.offsetHeight)/this.steps *(this.moveout?-1:1);
        this.dY0 = this.moveout * this.Getter.getAbsoluteTop(obj) + (this.moveout - 1) * this.clone.offsetHeight;        
        
        break;
        case 1:         
        this.dY = (this.Getter.getAbsoluteTop(this.clone) + 1.1 * this.obj.offsetHeight
                   - this.Getter.getWindowHeight()) / this.steps * (this.moveout?-1:1);
        
        if(this.moveout)        
        this.dY0 = this.Getter.getAbsoluteTop(this.clone);        
        else                     
        this.dY0 = this.Getter.getWindowHeight() - 1.1 * this.obj.offsetHeight;
        
        break;
        case 2:
        this.dY = 0;
        this.dY0 = this.Getter.getAbsoluteTop(obj);         
        break;
      }
      this.clone.style.left = this.Getter.Px(-1000);
      this.clone.style.top = this.Getter.Px(-1000);
    }

    this.display = function() {
      this.clone.style.left = this.Getter.Px(this.dX0 + this.dX * this.counter);
      this.clone.style.top = this.Getter.Px(this.dY0 + this.dY * this.counter);
    }
  }

/*
 * Notes for those who might want to add new effects
 *
 *
 * Below is an example of a basic effect derived from
 * effectBase. It will change the background of the
 * passed object on each step. 
 
 function basicEffect() {
   if(effectBase.call) 
   effectBase.call(this);    //inheriting from effectBase class

   this.init = function(obj) { 
     this.colours = new Array('white', 'red', 'blue', 'green', 'orange', 'black', 
                              'yellow', '#123456', '#cc00cc');
     this.length = this.colours.length;   
   }

   this.display = function() {   
     this.obj.style.background = this.colours[this.counter % this.length];
   }
 }

 * init is called once, when the effect starts. If possible, try to make your
 * code do the heavy stuff here and try to simplify the next function.
 * if init returns -1 the effect is canceled

 * display is called for each step of the effect. In order to get best effects
 * avoid using a lot of code here, and try keeping this function for the
 * stuff vital for the effect to take place

 * An example of how to call this for: <span id='test'>Basic Test</span>
 * <script>
 *     new basicEffect().go(document.getElementById('test'), 10000);
 * </script>
 */

/*
 * More advanced information:
 * 
 * The defined effect can override any of the methods defined in baseEffect
 * but this must be done with caution if its a method other than init or display
 * go - the main entry point
 * baseInit - initializes the members of the defined effect and calls the init
 *            method
 * initClone/removeClone - if you chosed to clone the object, it creates and 
 *            removes a clone that will replace the object during the effect's 
 *            life span. The clone will be created in the beginning and removed
 *            at the end of the effect. Its purpose is to preserve the initial
 *            display (useful for objects positioned relative);              
 * savecssText/restorecssText - its aim is to preserve the state of the object
 *            so that the effect wont do any undesired changes on it.
 * init/restore - are called when the effect starts and when its over
 *            normally the default behaviour of restore does the restoring but 
 *            if needed, it should be overriden
 * nextStep - it's the function triggered on the timer. It calls display() 
 *            for each step of the effect, and calls restore() when it's over
 */

