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