source: branches/new-datamodel/src/ajax/javascripts/external/scriptaculous/effects.js

Last change on this file was 26, checked in by lgiessmann, 16 years ago

added a license header to all files where the isidorus license is mentioned and referenced, in the ajax file there is also a reference to the MIT-license; an edit/create template for fragments is implemented in the ajax module, but it\'s really terrible and not really useable - this should be only the initial idea of realizing such a functionality\!

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