home *** CD-ROM | disk | FTP | other *** search
/ PC go! 2011 October / PCgo_1011_CD.iso / interface / js / effects.js next >
Encoding:
JavaScript  |  2007-07-09  |  37.3 KB  |  1,094 lines

  1. // script.aculo.us effects.js v1.7.1_beta2, Sat Apr 28 15:20:12 CEST 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 markerOffset 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();