tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

effects.js (38213B)


      1 // script.aculo.us effects.js v1.7.1_beta2, Tue May 15 15:15:45 EDT 2007
      2 
      3 // Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
      4 // Contributors:
      5 //  Justin Palmer (http://encytemedia.com/)
      6 //  Mark Pilgrim (http://diveintomark.org/)
      7 //  Martin Bialasinki
      8 // 
      9 // script.aculo.us is freely distributable under the terms of an MIT-style license.
     10 // For details, see the script.aculo.us web site: http://script.aculo.us/ 
     11 
     12 // converts rgb() and #xxx to #xxxxxx format,  
     13 // returns self (or first argument) if not convertable  
     14 String.prototype.parseColor = function() {  
     15  var color = '#';
     16  if(this.slice(0,4) == 'rgb(') {  
     17    var cols = this.slice(4,this.length-1).split(',');  
     18    var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);  
     19  } else {  
     20    if(this.slice(0,1) == '#') {  
     21      if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();  
     22      if(this.length==7) color = this.toLowerCase();  
     23    }  
     24  }  
     25  return(color.length==7 ? color : (arguments[0] || this));  
     26 }
     27 
     28 /*--------------------------------------------------------------------------*/
     29 
     30 Element.collectTextNodes = function(element) {  
     31  return $A($(element).childNodes).collect( function(node) {
     32    return (node.nodeType==3 ? node.nodeValue : 
     33      (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
     34  }).flatten().join('');
     35 }
     36 
     37 Element.collectTextNodesIgnoreClass = function(element, className) {  
     38  return $A($(element).childNodes).collect( function(node) {
     39    return (node.nodeType==3 ? node.nodeValue : 
     40      ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? 
     41        Element.collectTextNodesIgnoreClass(node, className) : ''));
     42  }).flatten().join('');
     43 }
     44 
     45 Element.setContentZoom = function(element, percent) {
     46  element = $(element);  
     47  element.setStyle({fontSize: (percent/100) + 'em'});   
     48  if(Prototype.Browser.WebKit) window.scrollBy(0,0);
     49  return element;
     50 }
     51 
     52 Element.getInlineOpacity = function(element){
     53  return $(element).style.opacity || '';
     54 }
     55 
     56 Element.forceRerendering = function(element) {
     57  try {
     58    element = $(element);
     59    var n = document.createTextNode(' ');
     60    element.appendChild(n);
     61    element.removeChild(n);
     62  } catch(e) { }
     63 };
     64 
     65 /*--------------------------------------------------------------------------*/
     66 
     67 Array.prototype.call = function() {
     68  var args = arguments;
     69  this.each(function(f){ f.apply(this, args) });
     70 }
     71 
     72 /*--------------------------------------------------------------------------*/
     73 
     74 var Effect = {
     75  _elementDoesNotExistError: {
     76    name: 'ElementDoesNotExistError',
     77    message: 'The specified DOM element does not exist, but is required for this effect to operate'
     78  },
     79  tagifyText: function(element) {
     80    if(typeof Builder == 'undefined')
     81      throw("Effect.tagifyText requires including script.aculo.us' builder.js library");
     82      
     83    var tagifyStyle = 'position:relative';
     84    if(Prototype.Browser.IE) tagifyStyle += ';zoom:1';
     85    
     86    element = $(element);
     87    $A(element.childNodes).each( function(child) {
     88      if(child.nodeType==3) {
     89        child.nodeValue.toArray().each( function(character) {
     90          element.insertBefore(
     91            Builder.node('span',{style: tagifyStyle},
     92              character == ' ' ? String.fromCharCode(160) : character), 
     93              child);
     94        });
     95        Element.remove(child);
     96      }
     97    });
     98  },
     99  multiple: function(element, effect) {
    100    var elements;
    101    if(((typeof element == 'object') || 
    102        (typeof element == 'function')) && 
    103       (element.length))
    104      elements = element;
    105    else
    106      elements = $(element).childNodes;
    107      
    108    var options = Object.extend({
    109      speed: 0.1,
    110      delay: 0.0
    111    }, arguments[2] || {});
    112    var masterDelay = options.delay;
    113 
    114    $A(elements).each( function(element, index) {
    115      new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
    116    });
    117  },
    118  PAIRS: {
    119    'slide':  ['SlideDown','SlideUp'],
    120    'blind':  ['BlindDown','BlindUp'],
    121    'appear': ['Appear','Fade']
    122  },
    123  toggle: function(element, effect) {
    124    element = $(element);
    125    effect = (effect || 'appear').toLowerCase();
    126    var options = Object.extend({
    127      queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
    128    }, arguments[2] || {});
    129    Effect[element.visible() ? 
    130      Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
    131  }
    132 };
    133 
    134 var Effect2 = Effect; // deprecated
    135 
    136 /* ------------- transitions ------------- */
    137 
    138 Effect.Transitions = {
    139  linear: Prototype.K,
    140  sinoidal: function(pos) {
    141    return (-Math.cos(pos*Math.PI)/2) + 0.5;
    142  },
    143  reverse: function(pos) {
    144    return 1-pos;
    145  },
    146  flicker: function(pos) {
    147    var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
    148    return (pos > 1 ? 1 : pos);
    149  },
    150  wobble: function(pos) {
    151    return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
    152  },
    153  pulse: function(pos, pulses) { 
    154    pulses = pulses || 5; 
    155    return (
    156      Math.round((pos % (1/pulses)) * pulses) == 0 ? 
    157            ((pos * pulses * 2) - Math.floor(pos * pulses * 2)) : 
    158        1 - ((pos * pulses * 2) - Math.floor(pos * pulses * 2))
    159      );
    160  },
    161  none: function(pos) {
    162    return 0;
    163  },
    164  full: function(pos) {
    165    return 1;
    166  }
    167 };
    168 
    169 /* ------------- core effects ------------- */
    170 
    171 Effect.ScopedQueue = Class.create();
    172 Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), {
    173  initialize: function() {
    174    this.effects  = [];
    175    this.interval = null;    
    176  },
    177  _each: function(iterator) {
    178    this.effects._each(iterator);
    179  },
    180  add: function(effect) {
    181    var timestamp = new Date().getTime();
    182    
    183    var position = (typeof effect.options.queue == 'string') ? 
    184      effect.options.queue : effect.options.queue.position;
    185    
    186    switch(position) {
    187      case 'front':
    188        // move unstarted effects after this effect  
    189        this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
    190            e.startOn  += effect.finishOn;
    191            e.finishOn += effect.finishOn;
    192          });
    193        break;
    194      case 'with-last':
    195        timestamp = this.effects.pluck('startOn').max() || timestamp;
    196        break;
    197      case 'end':
    198        // start effect after last queued effect has finished
    199        timestamp = this.effects.pluck('finishOn').max() || timestamp;
    200        break;
    201    }
    202    
    203    effect.startOn  += timestamp;
    204    effect.finishOn += timestamp;
    205 
    206    if(!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
    207      this.effects.push(effect);
    208    
    209    if(!this.interval)
    210      this.interval = setInterval(this.loop.bind(this), 15);
    211  },
    212  remove: function(effect) {
    213    this.effects = this.effects.reject(function(e) { return e==effect });
    214    if(this.effects.length == 0) {
    215      clearInterval(this.interval);
    216      this.interval = null;
    217    }
    218  },
    219  loop: function() {
    220    var timePos = new Date().getTime();
    221    for(var i=0, len=this.effects.length;i<len;i++) 
    222      this.effects[i] && this.effects[i].loop(timePos);
    223  }
    224 });
    225 
    226 Effect.Queues = {
    227  instances: $H(),
    228  get: function(queueName) {
    229    if(typeof queueName != 'string') return queueName;
    230    
    231    if(!this.instances[queueName])
    232      this.instances[queueName] = new Effect.ScopedQueue();
    233      
    234    return this.instances[queueName];
    235  }
    236 }
    237 Effect.Queue = Effect.Queues.get('global');
    238 
    239 Effect.DefaultOptions = {
    240  transition: Effect.Transitions.sinoidal,
    241  duration:   1.0,   // seconds
    242  fps:        100,   // 100= assume 66fps max.
    243  sync:       false, // true for combining
    244  from:       0.0,
    245  to:         1.0,
    246  delay:      0.0,
    247  queue:      'parallel'
    248 }
    249 
    250 Effect.Base = function() {};
    251 Effect.Base.prototype = {
    252  position: null,
    253  start: function(options) {
    254    function codeForEvent(options,eventName){
    255      return (
    256        (options[eventName+'Internal'] ? 'this.options.'+eventName+'Internal(this);' : '') +
    257        (options[eventName] ? 'this.options.'+eventName+'(this);' : '')
    258      );
    259    }
    260    if(options.transition === false) options.transition = Effect.Transitions.linear;
    261    this.options      = Object.extend(Object.extend({},Effect.DefaultOptions), options || {});
    262    this.currentFrame = 0;
    263    this.state        = 'idle';
    264    this.startOn      = this.options.delay*1000;
    265    this.finishOn     = this.startOn+(this.options.duration*1000);
    266    this.fromToDelta  = this.options.to-this.options.from;
    267    this.totalTime    = this.finishOn-this.startOn;
    268    this.totalFrames  = this.options.fps*this.options.duration;
    269    
    270    eval('this.render = function(pos){ '+
    271      'if(this.state=="idle"){this.state="running";'+
    272      codeForEvent(options,'beforeSetup')+
    273      (this.setup ? 'this.setup();':'')+ 
    274      codeForEvent(options,'afterSetup')+
    275      '};if(this.state=="running"){'+
    276      'pos=this.options.transition(pos)*'+this.fromToDelta+'+'+this.options.from+';'+
    277      'this.position=pos;'+
    278      codeForEvent(options,'beforeUpdate')+
    279      (this.update ? 'this.update(pos);':'')+
    280      codeForEvent(options,'afterUpdate')+
    281      '}}');
    282    
    283    this.event('beforeStart');
    284    if(!this.options.sync)
    285      Effect.Queues.get(typeof this.options.queue == 'string' ? 
    286        'global' : this.options.queue.scope).add(this);
    287  },
    288  loop: function(timePos) {
    289    if(timePos >= this.startOn) {
    290      if(timePos >= this.finishOn) {
    291        this.render(1.0);
    292        this.cancel();
    293        this.event('beforeFinish');
    294        if(this.finish) this.finish(); 
    295        this.event('afterFinish');
    296        return;  
    297      }
    298      var pos   = (timePos - this.startOn) / this.totalTime,
    299          frame = Math.round(pos * this.totalFrames);
    300      if(frame > this.currentFrame) {
    301        this.render(pos);
    302        this.currentFrame = frame;
    303      }
    304    }
    305  },
    306  cancel: function() {
    307    if(!this.options.sync)
    308      Effect.Queues.get(typeof this.options.queue == 'string' ? 
    309        'global' : this.options.queue.scope).remove(this);
    310    this.state = 'finished';
    311  },
    312  event: function(eventName) {
    313    if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
    314    if(this.options[eventName]) this.options[eventName](this);
    315  },
    316  inspect: function() {
    317    var data = $H();
    318    for(property in this)
    319      if(typeof this[property] != 'function') data[property] = this[property];
    320    return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';
    321  }
    322 }
    323 
    324 Effect.Parallel = Class.create();
    325 Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), {
    326  initialize: function(effects) {
    327    this.effects = effects || [];
    328    this.start(arguments[1]);
    329  },
    330  update: function(position) {
    331    this.effects.invoke('render', position);
    332  },
    333  finish: function(position) {
    334    this.effects.each( function(effect) {
    335      effect.render(1.0);
    336      effect.cancel();
    337      effect.event('beforeFinish');
    338      if(effect.finish) effect.finish(position);
    339      effect.event('afterFinish');
    340    });
    341  }
    342 });
    343 
    344 Effect.Event = Class.create();
    345 Object.extend(Object.extend(Effect.Event.prototype, Effect.Base.prototype), {
    346  initialize: function() {
    347    var options = Object.extend({
    348      duration: 0
    349    }, arguments[0] || {});
    350    this.start(options);
    351  },
    352  update: Prototype.emptyFunction
    353 });
    354 
    355 Effect.Opacity = Class.create();
    356 Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), {
    357  initialize: function(element) {
    358    this.element = $(element);
    359    if(!this.element) throw(Effect._elementDoesNotExistError);
    360    // make this work on IE on elements without 'layout'
    361    if(Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
    362      this.element.setStyle({zoom: 1});
    363    var options = Object.extend({
    364      from: this.element.getOpacity() || 0.0,
    365      to:   1.0
    366    }, arguments[1] || {});
    367    this.start(options);
    368  },
    369  update: function(position) {
    370    this.element.setOpacity(position);
    371  }
    372 });
    373 
    374 Effect.Move = Class.create();
    375 Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), {
    376  initialize: function(element) {
    377    this.element = $(element);
    378    if(!this.element) throw(Effect._elementDoesNotExistError);
    379    var options = Object.extend({
    380      x:    0,
    381      y:    0,
    382      mode: 'relative'
    383    }, arguments[1] || {});
    384    this.start(options);
    385  },
    386  setup: function() {
    387    // Bug in Opera: Opera returns the "real" position of a static element or
    388    // relative element that does not have top/left explicitly set.
    389    // ==> Always set top and left for position relative elements in your stylesheets 
    390    // (to 0 if you do not need them) 
    391    this.element.makePositioned();
    392    this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
    393    this.originalTop  = parseFloat(this.element.getStyle('top')  || '0');
    394    if(this.options.mode == 'absolute') {
    395      // absolute movement, so we need to calc deltaX and deltaY
    396      this.options.x = this.options.x - this.originalLeft;
    397      this.options.y = this.options.y - this.originalTop;
    398    }
    399  },
    400  update: function(position) {
    401    this.element.setStyle({
    402      left: Math.round(this.options.x  * position + this.originalLeft) + 'px',
    403      top:  Math.round(this.options.y  * position + this.originalTop)  + 'px'
    404    });
    405  }
    406 });
    407 
    408 // for backwards compatibility
    409 Effect.MoveBy = function(element, toTop, toLeft) {
    410  return new Effect.Move(element, 
    411    Object.extend({ x: toLeft, y: toTop }, arguments[3] || {}));
    412 };
    413 
    414 Effect.Scale = Class.create();
    415 Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
    416  initialize: function(element, percent) {
    417    this.element = $(element);
    418    if(!this.element) throw(Effect._elementDoesNotExistError);
    419    var options = Object.extend({
    420      scaleX: true,
    421      scaleY: true,
    422      scaleContent: true,
    423      scaleFromCenter: false,
    424      scaleMode: 'box',        // 'box' or 'contents' or {} with provided values
    425      scaleFrom: 100.0,
    426      scaleTo:   percent
    427    }, arguments[2] || {});
    428    this.start(options);
    429  },
    430  setup: function() {
    431    this.restoreAfterFinish = this.options.restoreAfterFinish || false;
    432    this.elementPositioning = this.element.getStyle('position');
    433    
    434    this.originalStyle = {};
    435    ['top','left','width','height','fontSize'].each( function(k) {
    436      this.originalStyle[k] = this.element.style[k];
    437    }.bind(this));
    438      
    439    this.originalTop  = this.element.offsetTop;
    440    this.originalLeft = this.element.offsetLeft;
    441    
    442    var fontSize = this.element.getStyle('font-size') || '100%';
    443    ['em','px','%','pt'].each( function(fontSizeType) {
    444      if(fontSize.indexOf(fontSizeType)>0) {
    445        this.fontSize     = parseFloat(fontSize);
    446        this.fontSizeType = fontSizeType;
    447      }
    448    }.bind(this));
    449    
    450    this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
    451    
    452    this.dims = null;
    453    if(this.options.scaleMode=='box')
    454      this.dims = [this.element.offsetHeight, this.element.offsetWidth];
    455    if(/^content/.test(this.options.scaleMode))
    456      this.dims = [this.element.scrollHeight, this.element.scrollWidth];
    457    if(!this.dims)
    458      this.dims = [this.options.scaleMode.originalHeight,
    459                   this.options.scaleMode.originalWidth];
    460  },
    461  update: function(position) {
    462    var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
    463    if(this.options.scaleContent && this.fontSize)
    464      this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
    465    this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
    466  },
    467  finish: function(position) {
    468    if(this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
    469  },
    470  setDimensions: function(height, width) {
    471    var d = {};
    472    if(this.options.scaleX) d.width = Math.round(width) + 'px';
    473    if(this.options.scaleY) d.height = Math.round(height) + 'px';
    474    if(this.options.scaleFromCenter) {
    475      var topd  = (height - this.dims[0])/2;
    476      var leftd = (width  - this.dims[1])/2;
    477      if(this.elementPositioning == 'absolute') {
    478        if(this.options.scaleY) d.top = this.originalTop-topd + 'px';
    479        if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
    480      } else {
    481        if(this.options.scaleY) d.top = -topd + 'px';
    482        if(this.options.scaleX) d.left = -leftd + 'px';
    483      }
    484    }
    485    this.element.setStyle(d);
    486  }
    487 });
    488 
    489 Effect.Highlight = Class.create();
    490 Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), {
    491  initialize: function(element) {
    492    this.element = $(element);
    493    if(!this.element) throw(Effect._elementDoesNotExistError);
    494    var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {});
    495    this.start(options);
    496  },
    497  setup: function() {
    498    // Prevent executing on elements not in the layout flow
    499    if(this.element.getStyle('display')=='none') { this.cancel(); return; }
    500    // Disable background image during the effect
    501    this.oldStyle = {};
    502    if (!this.options.keepBackgroundImage) {
    503      this.oldStyle.backgroundImage = this.element.getStyle('background-image');
    504      this.element.setStyle({backgroundImage: 'none'});
    505    }
    506    if(!this.options.endcolor)
    507      this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
    508    if(!this.options.restorecolor)
    509      this.options.restorecolor = this.element.getStyle('background-color');
    510    // init color calculations
    511    this._base  = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
    512    this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
    513  },
    514  update: function(position) {
    515    this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
    516      return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) });
    517  },
    518  finish: function() {
    519    this.element.setStyle(Object.extend(this.oldStyle, {
    520      backgroundColor: this.options.restorecolor
    521    }));
    522  }
    523 });
    524 
    525 Effect.ScrollTo = Class.create();
    526 Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
    527  initialize: function(element) {
    528    this.element = $(element);
    529    this.start(arguments[1] || {});
    530  },
    531  setup: function() {
    532    Position.prepare();
    533    var offsets = Position.cumulativeOffset(this.element);
    534    if(this.options.offset) offsets[1] += this.options.offset;
    535    var max = window.innerHeight ? 
    536      window.height - window.innerHeight :
    537      document.body.scrollHeight - 
    538        (document.documentElement.clientHeight ? 
    539          document.documentElement.clientHeight : document.body.clientHeight);
    540    this.scrollStart = Position.deltaY;
    541    this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart;
    542  },
    543  update: function(position) {
    544    Position.prepare();
    545    window.scrollTo(Position.deltaX, 
    546      this.scrollStart + (position*this.delta));
    547  }
    548 });
    549 
    550 /* ------------- combination effects ------------- */
    551 
    552 Effect.Fade = function(element) {
    553  element = $(element);
    554  var oldOpacity = element.getInlineOpacity();
    555  var options = Object.extend({
    556  from: element.getOpacity() || 1.0,
    557  to:   0.0,
    558  afterFinishInternal: function(effect) { 
    559    if(effect.options.to!=0) return;
    560    effect.element.hide().setStyle({opacity: oldOpacity}); 
    561  }}, arguments[1] || {});
    562  return new Effect.Opacity(element,options);
    563 }
    564 
    565 Effect.Appear = function(element) {
    566  element = $(element);
    567  var options = Object.extend({
    568  from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
    569  to:   1.0,
    570  // force Safari to render floated elements properly
    571  afterFinishInternal: function(effect) {
    572    effect.element.forceRerendering();
    573  },
    574  beforeSetup: function(effect) {
    575    effect.element.setOpacity(effect.options.from).show(); 
    576  }}, arguments[1] || {});
    577  return new Effect.Opacity(element,options);
    578 }
    579 
    580 Effect.Puff = function(element) {
    581  element = $(element);
    582  var oldStyle = { 
    583    opacity: element.getInlineOpacity(), 
    584    position: element.getStyle('position'),
    585    top:  element.style.top,
    586    left: element.style.left,
    587    width: element.style.width,
    588    height: element.style.height
    589  };
    590  return new Effect.Parallel(
    591   [ new Effect.Scale(element, 200, 
    592      { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), 
    593     new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], 
    594     Object.extend({ duration: 1.0, 
    595      beforeSetupInternal: function(effect) {
    596        Position.absolutize(effect.effects[0].element)
    597      },
    598      afterFinishInternal: function(effect) {
    599         effect.effects[0].element.hide().setStyle(oldStyle); }
    600     }, arguments[1] || {})
    601   );
    602 }
    603 
    604 Effect.BlindUp = function(element) {
    605  element = $(element);
    606  element.makeClipping();
    607  return new Effect.Scale(element, 0,
    608    Object.extend({ scaleContent: false, 
    609      scaleX: false, 
    610      restoreAfterFinish: true,
    611      afterFinishInternal: function(effect) {
    612        effect.element.hide().undoClipping();
    613      } 
    614    }, arguments[1] || {})
    615  );
    616 }
    617 
    618 Effect.BlindDown = function(element) {
    619  element = $(element);
    620  var elementDimensions = element.getDimensions();
    621  return new Effect.Scale(element, 100, Object.extend({ 
    622    scaleContent: false, 
    623    scaleX: false,
    624    scaleFrom: 0,
    625    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
    626    restoreAfterFinish: true,
    627    afterSetup: function(effect) {
    628      effect.element.makeClipping().setStyle({height: '0px'}).show(); 
    629    },  
    630    afterFinishInternal: function(effect) {
    631      effect.element.undoClipping();
    632    }
    633  }, arguments[1] || {}));
    634 }
    635 
    636 Effect.SwitchOff = function(element) {
    637  element = $(element);
    638  var oldOpacity = element.getInlineOpacity();
    639  return new Effect.Appear(element, Object.extend({
    640    duration: 0.4,
    641    from: 0,
    642    transition: Effect.Transitions.flicker,
    643    afterFinishInternal: function(effect) {
    644      new Effect.Scale(effect.element, 1, { 
    645        duration: 0.3, scaleFromCenter: true,
    646        scaleX: false, scaleContent: false, restoreAfterFinish: true,
    647        beforeSetup: function(effect) { 
    648          effect.element.makePositioned().makeClipping();
    649        },
    650        afterFinishInternal: function(effect) {
    651          effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
    652        }
    653      })
    654    }
    655  }, arguments[1] || {}));
    656 }
    657 
    658 Effect.DropOut = function(element) {
    659  element = $(element);
    660  var oldStyle = {
    661    top: element.getStyle('top'),
    662    left: element.getStyle('left'),
    663    opacity: element.getInlineOpacity() };
    664  return new Effect.Parallel(
    665    [ new Effect.Move(element, {x: 0, y: 100, sync: true }), 
    666      new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
    667    Object.extend(
    668      { duration: 0.5,
    669        beforeSetup: function(effect) {
    670          effect.effects[0].element.makePositioned(); 
    671        },
    672        afterFinishInternal: function(effect) {
    673          effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
    674        } 
    675      }, arguments[1] || {}));
    676 }
    677 
    678 Effect.Shake = function(element) {
    679  element = $(element);
    680  var oldStyle = {
    681    top: element.getStyle('top'),
    682    left: element.getStyle('left') };
    683    return new Effect.Move(element, 
    684      { x:  20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
    685    new Effect.Move(effect.element,
    686      { x: -40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {
    687    new Effect.Move(effect.element,
    688      { x:  40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {
    689    new Effect.Move(effect.element,
    690      { x: -40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {
    691    new Effect.Move(effect.element,
    692      { x:  40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {
    693    new Effect.Move(effect.element,
    694      { x: -20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
    695        effect.element.undoPositioned().setStyle(oldStyle);
    696  }}) }}) }}) }}) }}) }});
    697 }
    698 
    699 Effect.SlideDown = function(element) {
    700  element = $(element).cleanWhitespace();
    701  // SlideDown need to have the content of the element wrapped in a container element with fixed height!
    702  var oldInnerBottom = element.down().getStyle('bottom');
    703  var elementDimensions = element.getDimensions();
    704  return new Effect.Scale(element, 100, Object.extend({ 
    705    scaleContent: false, 
    706    scaleX: false, 
    707    scaleFrom: window.opera ? 0 : 1,
    708    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
    709    restoreAfterFinish: true,
    710    afterSetup: function(effect) {
    711      effect.element.makePositioned();
    712      effect.element.down().makePositioned();
    713      if(window.opera) effect.element.setStyle({top: ''});
    714      effect.element.makeClipping().setStyle({height: '0px'}).show(); 
    715    },
    716    afterUpdateInternal: function(effect) {
    717      effect.element.down().setStyle({bottom:
    718        (effect.dims[0] - effect.element.clientHeight) + 'px' }); 
    719    },
    720    afterFinishInternal: function(effect) {
    721      effect.element.undoClipping().undoPositioned();
    722      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
    723    }, arguments[1] || {})
    724  );
    725 }
    726 
    727 Effect.SlideUp = function(element) {
    728  element = $(element).cleanWhitespace();
    729  var oldInnerBottom = element.down().getStyle('bottom');
    730  return new Effect.Scale(element, window.opera ? 0 : 1,
    731   Object.extend({ scaleContent: false, 
    732    scaleX: false, 
    733    scaleMode: 'box',
    734    scaleFrom: 100,
    735    restoreAfterFinish: true,
    736    beforeStartInternal: function(effect) {
    737      effect.element.makePositioned();
    738      effect.element.down().makePositioned();
    739      if(window.opera) effect.element.setStyle({top: ''});
    740      effect.element.makeClipping().show();
    741    },  
    742    afterUpdateInternal: function(effect) {
    743      effect.element.down().setStyle({bottom:
    744        (effect.dims[0] - effect.element.clientHeight) + 'px' });
    745    },
    746    afterFinishInternal: function(effect) {
    747      effect.element.hide().undoClipping().undoPositioned().setStyle({bottom: oldInnerBottom});
    748      effect.element.down().undoPositioned();
    749    }
    750   }, arguments[1] || {})
    751  );
    752 }
    753 
    754 // Bug in opera makes the TD containing this element expand for a instance after finish 
    755 Effect.Squish = function(element) {
    756  return new Effect.Scale(element, window.opera ? 1 : 0, { 
    757    restoreAfterFinish: true,
    758    beforeSetup: function(effect) {
    759      effect.element.makeClipping(); 
    760    },  
    761    afterFinishInternal: function(effect) {
    762      effect.element.hide().undoClipping(); 
    763    }
    764  });
    765 }
    766 
    767 Effect.Grow = function(element) {
    768  element = $(element);
    769  var options = Object.extend({
    770    direction: 'center',
    771    moveTransition: Effect.Transitions.sinoidal,
    772    scaleTransition: Effect.Transitions.sinoidal,
    773    opacityTransition: Effect.Transitions.full
    774  }, arguments[1] || {});
    775  var oldStyle = {
    776    top: element.style.top,
    777    left: element.style.left,
    778    height: element.style.height,
    779    width: element.style.width,
    780    opacity: element.getInlineOpacity() };
    781 
    782  var dims = element.getDimensions();    
    783  var initialMoveX, initialMoveY;
    784  var moveX, moveY;
    785  
    786  switch (options.direction) {
    787    case 'top-left':
    788      initialMoveX = initialMoveY = moveX = moveY = 0; 
    789      break;
    790    case 'top-right':
    791      initialMoveX = dims.width;
    792      initialMoveY = moveY = 0;
    793      moveX = -dims.width;
    794      break;
    795    case 'bottom-left':
    796      initialMoveX = moveX = 0;
    797      initialMoveY = dims.height;
    798      moveY = -dims.height;
    799      break;
    800    case 'bottom-right':
    801      initialMoveX = dims.width;
    802      initialMoveY = dims.height;
    803      moveX = -dims.width;
    804      moveY = -dims.height;
    805      break;
    806    case 'center':
    807      initialMoveX = dims.width / 2;
    808      initialMoveY = dims.height / 2;
    809      moveX = -dims.width / 2;
    810      moveY = -dims.height / 2;
    811      break;
    812  }
    813  
    814  return new Effect.Move(element, {
    815    x: initialMoveX,
    816    y: initialMoveY,
    817    duration: 0.01, 
    818    beforeSetup: function(effect) {
    819      effect.element.hide().makeClipping().makePositioned();
    820    },
    821    afterFinishInternal: function(effect) {
    822      new Effect.Parallel(
    823        [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
    824          new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
    825          new Effect.Scale(effect.element, 100, {
    826            scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, 
    827            sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
    828        ], Object.extend({
    829             beforeSetup: function(effect) {
    830               effect.effects[0].element.setStyle({height: '0px'}).show(); 
    831             },
    832             afterFinishInternal: function(effect) {
    833               effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); 
    834             }
    835           }, options)
    836      )
    837    }
    838  });
    839 }
    840 
    841 Effect.Shrink = function(element) {
    842  element = $(element);
    843  var options = Object.extend({
    844    direction: 'center',
    845    moveTransition: Effect.Transitions.sinoidal,
    846    scaleTransition: Effect.Transitions.sinoidal,
    847    opacityTransition: Effect.Transitions.none
    848  }, arguments[1] || {});
    849  var oldStyle = {
    850    top: element.style.top,
    851    left: element.style.left,
    852    height: element.style.height,
    853    width: element.style.width,
    854    opacity: element.getInlineOpacity() };
    855 
    856  var dims = element.getDimensions();
    857  var moveX, moveY;
    858  
    859  switch (options.direction) {
    860    case 'top-left':
    861      moveX = moveY = 0;
    862      break;
    863    case 'top-right':
    864      moveX = dims.width;
    865      moveY = 0;
    866      break;
    867    case 'bottom-left':
    868      moveX = 0;
    869      moveY = dims.height;
    870      break;
    871    case 'bottom-right':
    872      moveX = dims.width;
    873      moveY = dims.height;
    874      break;
    875    case 'center':  
    876      moveX = dims.width / 2;
    877      moveY = dims.height / 2;
    878      break;
    879  }
    880  
    881  return new Effect.Parallel(
    882    [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
    883      new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
    884      new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
    885    ], Object.extend({            
    886         beforeStartInternal: function(effect) {
    887           effect.effects[0].element.makePositioned().makeClipping(); 
    888         },
    889         afterFinishInternal: function(effect) {
    890           effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
    891       }, options)
    892  );
    893 }
    894 
    895 Effect.Pulsate = function(element) {
    896  element = $(element);
    897  var options    = arguments[1] || {};
    898  var oldOpacity = element.getInlineOpacity();
    899  var transition = options.transition || Effect.Transitions.sinoidal;
    900  var reverser   = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) };
    901  reverser.bind(transition);
    902  return new Effect.Opacity(element, 
    903    Object.extend(Object.extend({  duration: 2.0, from: 0,
    904      afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
    905    }, options), {transition: reverser}));
    906 }
    907 
    908 Effect.Fold = function(element) {
    909  element = $(element);
    910  var oldStyle = {
    911    top: element.style.top,
    912    left: element.style.left,
    913    width: element.style.width,
    914    height: element.style.height };
    915  element.makeClipping();
    916  return new Effect.Scale(element, 5, Object.extend({   
    917    scaleContent: false,
    918    scaleX: false,
    919    afterFinishInternal: function(effect) {
    920    new Effect.Scale(element, 1, { 
    921      scaleContent: false, 
    922      scaleY: false,
    923      afterFinishInternal: function(effect) {
    924        effect.element.hide().undoClipping().setStyle(oldStyle);
    925      } });
    926  }}, arguments[1] || {}));
    927 };
    928 
    929 Effect.Morph = Class.create();
    930 Object.extend(Object.extend(Effect.Morph.prototype, Effect.Base.prototype), {
    931  initialize: function(element) {
    932    this.element = $(element);
    933    if(!this.element) throw(Effect._elementDoesNotExistError);
    934    var options = Object.extend({
    935      style: {}
    936    }, arguments[1] || {});
    937    if (typeof options.style == 'string') {
    938      if(options.style.indexOf(':') == -1) {
    939        var cssText = '', selector = '.' + options.style;
    940        $A(document.styleSheets).reverse().each(function(styleSheet) {
    941          if (styleSheet.cssRules) cssRules = styleSheet.cssRules;
    942          else if (styleSheet.rules) cssRules = styleSheet.rules;
    943          $A(cssRules).reverse().each(function(rule) {
    944            if (selector == rule.selectorText) {
    945              cssText = rule.style.cssText;
    946              throw $break;
    947            }
    948          });
    949          if (cssText) throw $break;
    950        });
    951        this.style = cssText.parseStyle();
    952        options.afterFinishInternal = function(effect){
    953          effect.element.addClassName(effect.options.style);
    954          effect.transforms.each(function(transform) {
    955            if(transform.style != 'opacity')
    956              effect.element.style[transform.style] = '';
    957          });
    958        }
    959      } else this.style = options.style.parseStyle();
    960    } else this.style = $H(options.style)
    961    this.start(options);
    962  },
    963  setup: function(){
    964    function parseColor(color){
    965      if(!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
    966      color = color.parseColor();
    967      return $R(0,2).map(function(i){
    968        return parseInt( color.slice(i*2+1,i*2+3), 16 ) 
    969      });
    970    }
    971    this.transforms = this.style.map(function(pair){
    972      var property = pair[0], value = pair[1], unit = null;
    973 
    974      if(value.parseColor('#zzzzzz') != '#zzzzzz') {
    975        value = value.parseColor();
    976        unit  = 'color';
    977      } else if(property == 'opacity') {
    978        value = parseFloat(value);
    979        if(Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
    980          this.element.setStyle({zoom: 1});
    981      } else if(Element.CSS_LENGTH.test(value)) {
    982          var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
    983          value = parseFloat(components[1]);
    984          unit = (components.length == 3) ? components[2] : null;
    985      }
    986 
    987      var originalValue = this.element.getStyle(property);
    988      return { 
    989        style: property.camelize(), 
    990        originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0), 
    991        targetValue: unit=='color' ? parseColor(value) : value,
    992        unit: unit
    993      };
    994    }.bind(this)).reject(function(transform){
    995      return (
    996        (transform.originalValue == transform.targetValue) ||
    997        (
    998          transform.unit != 'color' &&
    999          (isNaN(transform.originalValue) || isNaN(transform.targetValue))
   1000        )
   1001      )
   1002    });
   1003  },
   1004  update: function(position) {
   1005    var style = {}, transform, i = this.transforms.length;
   1006    while(i--)
   1007      style[(transform = this.transforms[i]).style] = 
   1008        transform.unit=='color' ? '#'+
   1009          (Math.round(transform.originalValue[0]+
   1010            (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +
   1011          (Math.round(transform.originalValue[1]+
   1012            (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() +
   1013          (Math.round(transform.originalValue[2]+
   1014            (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :
   1015        transform.originalValue + Math.round(
   1016          ((transform.targetValue - transform.originalValue) * position) * 1000)/1000 + transform.unit;
   1017    this.element.setStyle(style, true);
   1018  }
   1019 });
   1020 
   1021 Effect.Transform = Class.create();
   1022 Object.extend(Effect.Transform.prototype, {
   1023  initialize: function(tracks){
   1024    this.tracks  = [];
   1025    this.options = arguments[1] || {};
   1026    this.addTracks(tracks);
   1027  },
   1028  addTracks: function(tracks){
   1029    tracks.each(function(track){
   1030      var data = $H(track).values().first();
   1031      this.tracks.push($H({
   1032        ids:     $H(track).keys().first(),
   1033        effect:  Effect.Morph,
   1034        options: { style: data }
   1035      }));
   1036    }.bind(this));
   1037    return this;
   1038  },
   1039  play: function(){
   1040    return new Effect.Parallel(
   1041      this.tracks.map(function(track){
   1042        var elements = [$(track.ids) || $$(track.ids)].flatten();
   1043        return elements.map(function(e){ return new track.effect(e, Object.extend({ sync:true }, track.options)) });
   1044      }).flatten(),
   1045      this.options
   1046    );
   1047  }
   1048 });
   1049 
   1050 Element.CSS_PROPERTIES = $w(
   1051  'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' + 
   1052  'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
   1053  'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
   1054  'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
   1055  'fontSize fontWeight height left letterSpacing lineHeight ' +
   1056  'marginBottom marginLeft marginRight marginTop maxHeight '+
   1057  'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
   1058  'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
   1059  'right textIndent top width wordSpacing zIndex');
   1060  
   1061 Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
   1062 
   1063 String.prototype.parseStyle = function(){
   1064  var element = document.createElement('div');
   1065  element.innerHTML = '<div style="' + this + '"></div>';
   1066  var style = element.childNodes[0].style, styleRules = $H();
   1067  
   1068  Element.CSS_PROPERTIES.each(function(property){
   1069    if(style[property]) styleRules[property] = style[property]; 
   1070  });
   1071  if(Prototype.Browser.IE && this.indexOf('opacity') > -1) {
   1072    styleRules.opacity = this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1];
   1073  }
   1074  return styleRules;
   1075 };
   1076 
   1077 Element.morph = function(element, style) {
   1078  new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || {}));
   1079  return element;
   1080 };
   1081 
   1082 ['getInlineOpacity','forceRerendering','setContentZoom',
   1083 'collectTextNodes','collectTextNodesIgnoreClass','morph'].each( 
   1084  function(f) { Element.Methods[f] = Element[f]; }
   1085 );
   1086 
   1087 Element.Methods.visualEffect = function(element, effect, options) {
   1088  s = effect.dasherize().camelize();
   1089  effect_class = s.charAt(0).toUpperCase() + s.substring(1);
   1090  new Effect[effect_class](element, options);
   1091  return $(element);
   1092 };
   1093 
   1094 Element.addMethods();