Project

General

Profile

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