1 // script.aculo.us effects.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009
3 // Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
5 // Justin Palmer (http://encytemedia.com/)
6 // Mark Pilgrim (http://diveintomark.org/)
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/
12 // converts rgb() and #xxx to #xxxxxx format,
13 // returns self (or first argument) if not convertable
14 String
.prototype.parseColor = function() {
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);
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();
25 return (color
.length
==7 ? color
: (arguments
[0] || this));
28 /*--------------------------------------------------------------------------*/
30 Element
.collectTextNodes = function(element
) {
31 return $A($(element
).childNodes
).collect( function(node
) {
32 return (node
.nodeType
==3 ? node
.nodeValue
:
33 (node
.hasChildNodes() ? Element
.collectTextNodes(node
) : ''));
34 }).flatten().join('');
37 Element
.collectTextNodesIgnoreClass = function(element
, className
) {
38 return $A($(element
).childNodes
).collect( function(node
) {
39 return (node
.nodeType
==3 ? node
.nodeValue
:
40 ((node
.hasChildNodes() && !Element
.hasClassName(node
,className
)) ?
41 Element
.collectTextNodesIgnoreClass(node
, className
) : ''));
42 }).flatten().join('');
45 Element
.setContentZoom = function(element
, percent
) {
47 element
.setStyle({fontSize
: (percent
/100) + 'em'});
48 if (Prototype
.Browser
.WebKit
) window
.scrollBy(0,0);
52 Element
.getInlineOpacity = function(element
){
53 return $(element
).style
.opacity
|| '';
56 Element
.forceRerendering = function(element
) {
59 var n
= document
.createTextNode(' ');
60 element
.appendChild(n
);
61 element
.removeChild(n
);
65 /*--------------------------------------------------------------------------*/
68 _elementDoesNotExistError
: {
69 name
: 'ElementDoesNotExistError',
70 message
: 'The specified DOM element does not exist, but is required for this effect to operate'
74 sinoidal: function(pos
) {
75 return (-Math
.cos(pos
*Math
.PI
)/2) + .5;
77 reverse: function(pos
) {
80 flicker: function(pos
) {
81 var pos
= ((-Math
.cos(pos
*Math
.PI
)/4) + .75) + Math.random()/4;
82 return pos
> 1 ? 1 : pos
;
84 wobble: function(pos
) {
85 return (-Math
.cos(pos
*Math
.PI
*(9*pos
))/2) + .5;
87 pulse: function(pos
, pulses
) {
88 return (-Math
.cos((pos
*((pulses
||5)-.5)*2)*Math
.PI
)/2) + .5;
90 spring: function(pos
) {
91 return 1 - (Math
.cos(pos
* 4.5 * Math
.PI
) * Math
.exp(-pos
* 6));
101 duration
: 1.0, // seconds
102 fps
: 100, // 100= assume 66fps max.
103 sync
: false, // true for combining
109 tagifyText: function(element
) {
110 var tagifyStyle
= 'position:relative';
111 if (Prototype
.Browser
.IE
) tagifyStyle
+= ';zoom:1';
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
),
122 Element
.remove(child
);
126 multiple: function(element
, effect
) {
128 if (((typeof element
== 'object') ||
129 Object
.isFunction(element
)) &&
133 elements
= $(element
).childNodes
;
135 var options
= Object
.extend({
138 }, arguments
[2] || { });
139 var masterDelay
= options
.delay
;
141 $A(elements
).each( function(element
, index
) {
142 new effect(element
, Object
.extend(options
, { delay
: index
* options
.speed
+ masterDelay
}));
146 'slide': ['SlideDown','SlideUp'],
147 'blind': ['BlindDown','BlindUp'],
148 'appear': ['Appear','Fade']
150 toggle: function(element
, effect
, options
) {
151 element
= $(element
);
152 effect
= (effect
|| 'appear').toLowerCase();
154 return Effect
[ Effect
.PAIRS
[ effect
][ element
.visible() ? 1 : 0 ] ](element
, Object
.extend({
155 queue
: { position
:'end', scope
:(element
.id
|| 'global'), limit
: 1 }
160 Effect
.DefaultOptions
.transition
= Effect
.Transitions
.sinoidal
;
162 /* ------------- core effects ------------- */
164 Effect
.ScopedQueue
= Class
.create(Enumerable
, {
165 initialize: function() {
167 this.interval
= null;
169 _each: function(iterator
) {
170 this.effects
._each(iterator
);
172 add: function(effect
) {
173 var timestamp
= new Date().getTime();
175 var position
= Object
.isString(effect
.options
.queue
) ?
176 effect
.options
.queue
: effect
.options
.queue
.position
;
180 // move unstarted effects after this effect
181 this.effects
.findAll(function(e
){ return e
.state
=='idle' }).each( function(e
) {
182 e
.startOn
+= effect
.finishOn
;
183 e
.finishOn
+= effect
.finishOn
;
187 timestamp
= this.effects
.pluck('startOn').max() || timestamp
;
190 // start effect after last queued effect has finished
191 timestamp
= this.effects
.pluck('finishOn').max() || timestamp
;
195 effect
.startOn
+= timestamp
;
196 effect
.finishOn
+= timestamp
;
198 if (!effect
.options
.queue
.limit
|| (this.effects
.length
< effect
.options
.queue
.limit
))
199 this.effects
.push(effect
);
202 this.interval
= setInterval(this.loop
.bind(this), 15);
204 remove: function(effect
) {
205 this.effects
= this.effects
.reject(function(e
) { return e
==effect
});
206 if (this.effects
.length
== 0) {
207 clearInterval(this.interval
);
208 this.interval
= null;
212 var timePos
= new Date().getTime();
213 for(var i
=0, len
=this.effects
.length
;i
<len
;i
++)
214 this.effects
[i
] && this.effects
[i
].loop(timePos
);
220 get: function(queueName
) {
221 if (!Object
.isString(queueName
)) return queueName
;
223 return this.instances
.get(queueName
) ||
224 this.instances
.set(queueName
, new Effect
.ScopedQueue());
227 Effect
.Queue
= Effect
.Queues
.get('global');
229 Effect
.Base
= Class
.create({
231 start: function(options
) {
232 if (options
&& options
.transition
=== false) options
.transition
= Effect
.Transitions
.linear
;
233 this.options
= Object
.extend(Object
.extend({ },Effect
.DefaultOptions
), options
|| { });
234 this.currentFrame
= 0;
236 this.startOn
= this.options
.delay
*1000;
237 this.finishOn
= this.startOn
+(this.options
.duration
*1000);
238 this.fromToDelta
= this.options
.to
-this.options
.from;
239 this.totalTime
= this.finishOn
-this.startOn
;
240 this.totalFrames
= this.options
.fps
*this.options
.duration
;
242 this.render
= (function() {
243 function dispatch(effect
, eventName
) {
244 if (effect
.options
[eventName
+ 'Internal'])
245 effect
.options
[eventName
+ 'Internal'](effect
);
246 if (effect
.options
[eventName
])
247 effect
.options
[eventName
](effect
);
250 return function(pos
) {
251 if (this.state
=== "idle") {
252 this.state
= "running";
253 dispatch(this, 'beforeSetup');
254 if (this.setup
) this.setup();
255 dispatch(this, 'afterSetup');
257 if (this.state
=== "running") {
258 pos
= (this.options
.transition(pos
) * this.fromToDelta
) + this.options
.from;
260 dispatch(this, 'beforeUpdate');
261 if (this.update
) this.update(pos
);
262 dispatch(this, 'afterUpdate');
267 this.event('beforeStart');
268 if (!this.options
.sync
)
269 Effect
.Queues
.get(Object
.isString(this.options
.queue
) ?
270 'global' : this.options
.queue
.scope
).add(this);
272 loop: function(timePos
) {
273 if (timePos
>= this.startOn
) {
274 if (timePos
>= this.finishOn
) {
277 this.event('beforeFinish');
278 if (this.finish
) this.finish();
279 this.event('afterFinish');
282 var pos
= (timePos
- this.startOn
) / this.totalTime
,
283 frame
= (pos
* this.totalFrames
).round();
284 if (frame
> this.currentFrame
) {
286 this.currentFrame
= frame
;
291 if (!this.options
.sync
)
292 Effect
.Queues
.get(Object
.isString(this.options
.queue
) ?
293 'global' : this.options
.queue
.scope
).remove(this);
294 this.state
= 'finished';
296 event: function(eventName
) {
297 if (this.options
[eventName
+ 'Internal']) this.options
[eventName
+ 'Internal'](this);
298 if (this.options
[eventName
]) this.options
[eventName
](this);
300 inspect: function() {
302 for(property
in this)
303 if (!Object
.isFunction(this[property
])) data
.set(property
, this[property
]);
304 return '#<Effect:' + data
.inspect() + ',options:' + $H(this.options
).inspect() + '>';
308 Effect
.Parallel
= Class
.create(Effect
.Base
, {
309 initialize: function(effects
) {
310 this.effects
= effects
|| [];
311 this.start(arguments
[1]);
313 update: function(position
) {
314 this.effects
.invoke('render', position
);
316 finish: function(position
) {
317 this.effects
.each( function(effect
) {
320 effect
.event('beforeFinish');
321 if (effect
.finish
) effect
.finish(position
);
322 effect
.event('afterFinish');
327 Effect
.Tween
= Class
.create(Effect
.Base
, {
328 initialize: function(object
, from, to
) {
329 object
= Object
.isString(object
) ? $(object
) : object
;
330 var args
= $A(arguments
), method
= args
.last(),
331 options
= args
.length
== 5 ? args
[3] : null;
332 this.method
= Object
.isFunction(method
) ? method
.bind(object
) :
333 Object
.isFunction(object
[method
]) ? object
[method
].bind(object
) :
334 function(value
) { object
[method
] = value
};
335 this.start(Object
.extend({ from: from, to
: to
}, options
|| { }));
337 update: function(position
) {
338 this.method(position
);
342 Effect
.Event
= Class
.create(Effect
.Base
, {
343 initialize: function() {
344 this.start(Object
.extend({ duration
: 0 }, arguments
[0] || { }));
346 update
: Prototype
.emptyFunction
349 Effect
.Opacity
= Class
.create(Effect
.Base
, {
350 initialize: function(element
) {
351 this.element
= $(element
);
352 if (!this.element
) throw(Effect
._elementDoesNotExistError
);
353 // make this work on IE on elements without 'layout'
354 if (Prototype
.Browser
.IE
&& (!this.element
.currentStyle
.hasLayout
))
355 this.element
.setStyle({zoom
: 1});
356 var options
= Object
.extend({
357 from: this.element
.getOpacity() || 0.0,
359 }, arguments
[1] || { });
362 update: function(position
) {
363 this.element
.setOpacity(position
);
367 Effect
.Move
= Class
.create(Effect
.Base
, {
368 initialize: function(element
) {
369 this.element
= $(element
);
370 if (!this.element
) throw(Effect
._elementDoesNotExistError
);
371 var options
= Object
.extend({
375 }, arguments
[1] || { });
379 this.element
.makePositioned();
380 this.originalLeft
= parseFloat(this.element
.getStyle('left') || '0');
381 this.originalTop
= parseFloat(this.element
.getStyle('top') || '0');
382 if (this.options
.mode
== 'absolute') {
383 this.options
.x
= this.options
.x
- this.originalLeft
;
384 this.options
.y
= this.options
.y
- this.originalTop
;
387 update: function(position
) {
388 this.element
.setStyle({
389 left
: (this.options
.x
* position
+ this.originalLeft
).round() + 'px',
390 top
: (this.options
.y
* position
+ this.originalTop
).round() + 'px'
395 // for backwards compatibility
396 Effect
.MoveBy = function(element
, toTop
, toLeft
) {
397 return new Effect
.Move(element
,
398 Object
.extend({ x
: toLeft
, y
: toTop
}, arguments
[3] || { }));
401 Effect
.Scale
= Class
.create(Effect
.Base
, {
402 initialize: function(element
, percent
) {
403 this.element
= $(element
);
404 if (!this.element
) throw(Effect
._elementDoesNotExistError
);
405 var options
= Object
.extend({
409 scaleFromCenter
: false,
410 scaleMode
: 'box', // 'box' or 'contents' or { } with provided values
413 }, arguments
[2] || { });
417 this.restoreAfterFinish
= this.options
.restoreAfterFinish
|| false;
418 this.elementPositioning
= this.element
.getStyle('position');
420 this.originalStyle
= { };
421 ['top','left','width','height','fontSize'].each( function(k
) {
422 this.originalStyle
[k
] = this.element
.style
[k
];
425 this.originalTop
= this.element
.offsetTop
;
426 this.originalLeft
= this.element
.offsetLeft
;
428 var fontSize
= this.element
.getStyle('font-size') || '100%';
429 ['em','px','%','pt'].each( function(fontSizeType
) {
430 if (fontSize
.indexOf(fontSizeType
)>0) {
431 this.fontSize
= parseFloat(fontSize
);
432 this.fontSizeType
= fontSizeType
;
436 this.factor
= (this.options
.scaleTo
- this.options
.scaleFrom
)/100;
439 if (this.options
.scaleMode
=='box')
440 this.dims
= [this.element
.offsetHeight
, this.element
.offsetWidth
];
441 if (/^content/.test(this.options
.scaleMode
))
442 this.dims
= [this.element
.scrollHeight
, this.element
.scrollWidth
];
444 this.dims
= [this.options
.scaleMode
.originalHeight
,
445 this.options
.scaleMode
.originalWidth
];
447 update: function(position
) {
448 var currentScale
= (this.options
.scaleFrom
/100.0) + (this.factor
* position
);
449 if (this.options
.scaleContent
&& this.fontSize
)
450 this.element
.setStyle({fontSize
: this.fontSize
* currentScale
+ this.fontSizeType
});
451 this.setDimensions(this.dims
[0] * currentScale
, this.dims
[1] * currentScale
);
453 finish: function(position
) {
454 if (this.restoreAfterFinish
) this.element
.setStyle(this.originalStyle
);
456 setDimensions: function(height
, width
) {
458 if (this.options
.scaleX
) d
.width
= width
.round() + 'px';
459 if (this.options
.scaleY
) d
.height
= height
.round() + 'px';
460 if (this.options
.scaleFromCenter
) {
461 var topd
= (height
- this.dims
[0])/2;
462 var leftd
= (width
- this.dims
[1])/2;
463 if (this.elementPositioning
== 'absolute') {
464 if (this.options
.scaleY
) d
.top
= this.originalTop
-topd
+ 'px';
465 if (this.options
.scaleX
) d
.left
= this.originalLeft
-leftd
+ 'px';
467 if (this.options
.scaleY
) d
.top
= -topd
+ 'px';
468 if (this.options
.scaleX
) d
.left
= -leftd
+ 'px';
471 this.element
.setStyle(d
);
475 Effect
.Highlight
= Class
.create(Effect
.Base
, {
476 initialize: function(element
) {
477 this.element
= $(element
);
478 if (!this.element
) throw(Effect
._elementDoesNotExistError
);
479 var options
= Object
.extend({ startcolor
: '#ffff99' }, arguments
[1] || { });
483 // Prevent executing on elements not in the layout flow
484 if (this.element
.getStyle('display')=='none') { this.cancel(); return; }
485 // Disable background image during the effect
487 if (!this.options
.keepBackgroundImage
) {
488 this.oldStyle
.backgroundImage
= this.element
.getStyle('background-image');
489 this.element
.setStyle({backgroundImage
: 'none'});
491 if (!this.options
.endcolor
)
492 this.options
.endcolor
= this.element
.getStyle('background-color').parseColor('#ffffff');
493 if (!this.options
.restorecolor
)
494 this.options
.restorecolor
= this.element
.getStyle('background-color');
495 // init color calculations
496 this._base
= $R(0,2).map(function(i
){ return parseInt(this.options
.startcolor
.slice(i
*2+1,i
*2+3),16) }.bind(this));
497 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));
499 update: function(position
) {
500 this.element
.setStyle({backgroundColor
: $R(0,2).inject('#',function(m
,v
,i
){
501 return m
+((this._base
[i
]+(this._delta
[i
]*position
)).round().toColorPart()); }.bind(this)) });
504 this.element
.setStyle(Object
.extend(this.oldStyle
, {
505 backgroundColor
: this.options
.restorecolor
510 Effect
.ScrollTo = function(element
) {
511 var options
= arguments
[1] || { },
512 scrollOffsets
= document
.viewport
.getScrollOffsets(),
513 elementOffsets
= $(element
).cumulativeOffset();
515 if (options
.offset
) elementOffsets
[1] += options
.offset
;
517 return new Effect
.Tween(null,
521 function(p
){ scrollTo(scrollOffsets
.left
, p
.round()); }
525 /* ------------- combination effects ------------- */
527 Effect
.Fade = function(element
) {
528 element
= $(element
);
529 var oldOpacity
= element
.getInlineOpacity();
530 var options
= Object
.extend({
531 from: element
.getOpacity() || 1.0,
533 afterFinishInternal: function(effect
) {
534 if (effect
.options
.to
!=0) return;
535 effect
.element
.hide().setStyle({opacity
: oldOpacity
});
537 }, arguments
[1] || { });
538 return new Effect
.Opacity(element
,options
);
541 Effect
.Appear = function(element
) {
542 element
= $(element
);
543 var options
= Object
.extend({
544 from: (element
.getStyle('display') == 'none' ? 0.0 : element
.getOpacity() || 0.0),
546 // force Safari to render floated elements properly
547 afterFinishInternal: function(effect
) {
548 effect
.element
.forceRerendering();
550 beforeSetup: function(effect
) {
551 effect
.element
.setOpacity(effect
.options
.from).show();
552 }}, arguments
[1] || { });
553 return new Effect
.Opacity(element
,options
);
556 Effect
.Puff = function(element
) {
557 element
= $(element
);
559 opacity
: element
.getInlineOpacity(),
560 position
: element
.getStyle('position'),
561 top
: element
.style
.top
,
562 left
: element
.style
.left
,
563 width
: element
.style
.width
,
564 height
: element
.style
.height
566 return new Effect
.Parallel(
567 [ new Effect
.Scale(element
, 200,
568 { sync
: true, scaleFromCenter
: true, scaleContent
: true, restoreAfterFinish
: true }),
569 new Effect
.Opacity(element
, { sync
: true, to
: 0.0 } ) ],
570 Object
.extend({ duration
: 1.0,
571 beforeSetupInternal: function(effect
) {
572 Position
.absolutize(effect
.effects
[0].element
);
574 afterFinishInternal: function(effect
) {
575 effect
.effects
[0].element
.hide().setStyle(oldStyle
); }
576 }, arguments
[1] || { })
580 Effect
.BlindUp = function(element
) {
581 element
= $(element
);
582 element
.makeClipping();
583 return new Effect
.Scale(element
, 0,
584 Object
.extend({ scaleContent
: false,
586 restoreAfterFinish
: true,
587 afterFinishInternal: function(effect
) {
588 effect
.element
.hide().undoClipping();
590 }, arguments
[1] || { })
594 Effect
.BlindDown = function(element
) {
595 element
= $(element
);
596 var elementDimensions
= element
.getDimensions();
597 return new Effect
.Scale(element
, 100, Object
.extend({
601 scaleMode
: {originalHeight
: elementDimensions
.height
, originalWidth
: elementDimensions
.width
},
602 restoreAfterFinish
: true,
603 afterSetup: function(effect
) {
604 effect
.element
.makeClipping().setStyle({height
: '0px'}).show();
606 afterFinishInternal: function(effect
) {
607 effect
.element
.undoClipping();
609 }, arguments
[1] || { }));
612 Effect
.SwitchOff = function(element
) {
613 element
= $(element
);
614 var oldOpacity
= element
.getInlineOpacity();
615 return new Effect
.Appear(element
, Object
.extend({
618 transition
: Effect
.Transitions
.flicker
,
619 afterFinishInternal: function(effect
) {
620 new Effect
.Scale(effect
.element
, 1, {
621 duration
: 0.3, scaleFromCenter
: true,
622 scaleX
: false, scaleContent
: false, restoreAfterFinish
: true,
623 beforeSetup: function(effect
) {
624 effect
.element
.makePositioned().makeClipping();
626 afterFinishInternal: function(effect
) {
627 effect
.element
.hide().undoClipping().undoPositioned().setStyle({opacity
: oldOpacity
});
631 }, arguments
[1] || { }));
634 Effect
.DropOut = function(element
) {
635 element
= $(element
);
637 top
: element
.getStyle('top'),
638 left
: element
.getStyle('left'),
639 opacity
: element
.getInlineOpacity() };
640 return new Effect
.Parallel(
641 [ new Effect
.Move(element
, {x
: 0, y
: 100, sync
: true }),
642 new Effect
.Opacity(element
, { sync
: true, to
: 0.0 }) ],
645 beforeSetup: function(effect
) {
646 effect
.effects
[0].element
.makePositioned();
648 afterFinishInternal: function(effect
) {
649 effect
.effects
[0].element
.hide().undoPositioned().setStyle(oldStyle
);
651 }, arguments
[1] || { }));
654 Effect
.Shake = function(element
) {
655 element
= $(element
);
656 var options
= Object
.extend({
659 }, arguments
[1] || {});
660 var distance
= parseFloat(options
.distance
);
661 var split
= parseFloat(options
.duration
) / 10.0;
663 top
: element
.getStyle('top'),
664 left
: element
.getStyle('left') };
665 return new Effect
.Move(element
,
666 { x
: distance
, y
: 0, duration
: split
, afterFinishInternal: function(effect
) {
667 new Effect
.Move(effect
.element
,
668 { x
: -distance
*2, y
: 0, duration
: split
*2, afterFinishInternal: function(effect
) {
669 new Effect
.Move(effect
.element
,
670 { x
: distance
*2, y
: 0, duration
: split
*2, afterFinishInternal: function(effect
) {
671 new Effect
.Move(effect
.element
,
672 { x
: -distance
*2, y
: 0, duration
: split
*2, afterFinishInternal: function(effect
) {
673 new Effect
.Move(effect
.element
,
674 { x
: distance
*2, y
: 0, duration
: split
*2, afterFinishInternal: function(effect
) {
675 new Effect
.Move(effect
.element
,
676 { x
: -distance
, y
: 0, duration
: split
, afterFinishInternal: function(effect
) {
677 effect
.element
.undoPositioned().setStyle(oldStyle
);
678 }}); }}); }}); }}); }}); }});
681 Effect
.SlideDown = function(element
) {
682 element
= $(element
).cleanWhitespace();
683 // SlideDown need to have the content of the element wrapped in a container element with fixed height!
684 var oldInnerBottom
= element
.down().getStyle('bottom');
685 var elementDimensions
= element
.getDimensions();
686 return new Effect
.Scale(element
, 100, Object
.extend({
689 scaleFrom
: window
.opera
? 0 : 1,
690 scaleMode
: {originalHeight
: elementDimensions
.height
, originalWidth
: elementDimensions
.width
},
691 restoreAfterFinish
: true,
692 afterSetup: function(effect
) {
693 effect
.element
.makePositioned();
694 effect
.element
.down().makePositioned();
695 if (window
.opera
) effect
.element
.setStyle({top
: ''});
696 effect
.element
.makeClipping().setStyle({height
: '0px'}).show();
698 afterUpdateInternal: function(effect
) {
699 effect
.element
.down().setStyle({bottom
:
700 (effect
.dims
[0] - effect
.element
.clientHeight
) + 'px' });
702 afterFinishInternal: function(effect
) {
703 effect
.element
.undoClipping().undoPositioned();
704 effect
.element
.down().undoPositioned().setStyle({bottom
: oldInnerBottom
}); }
705 }, arguments
[1] || { })
709 Effect
.SlideUp = function(element
) {
710 element
= $(element
).cleanWhitespace();
711 var oldInnerBottom
= element
.down().getStyle('bottom');
712 var elementDimensions
= element
.getDimensions();
713 return new Effect
.Scale(element
, window
.opera
? 0 : 1,
714 Object
.extend({ scaleContent
: false,
718 scaleMode
: {originalHeight
: elementDimensions
.height
, originalWidth
: elementDimensions
.width
},
719 restoreAfterFinish
: true,
720 afterSetup: function(effect
) {
721 effect
.element
.makePositioned();
722 effect
.element
.down().makePositioned();
723 if (window
.opera
) effect
.element
.setStyle({top
: ''});
724 effect
.element
.makeClipping().show();
726 afterUpdateInternal: function(effect
) {
727 effect
.element
.down().setStyle({bottom
:
728 (effect
.dims
[0] - effect
.element
.clientHeight
) + 'px' });
730 afterFinishInternal: function(effect
) {
731 effect
.element
.hide().undoClipping().undoPositioned();
732 effect
.element
.down().undoPositioned().setStyle({bottom
: oldInnerBottom
});
734 }, arguments
[1] || { })
738 // Bug in opera makes the TD containing this element expand for a instance after finish
739 Effect
.Squish = function(element
) {
740 return new Effect
.Scale(element
, window
.opera
? 1 : 0, {
741 restoreAfterFinish
: true,
742 beforeSetup: function(effect
) {
743 effect
.element
.makeClipping();
745 afterFinishInternal: function(effect
) {
746 effect
.element
.hide().undoClipping();
751 Effect
.Grow = function(element
) {
752 element
= $(element
);
753 var options
= Object
.extend({
755 moveTransition
: Effect
.Transitions
.sinoidal
,
756 scaleTransition
: Effect
.Transitions
.sinoidal
,
757 opacityTransition
: Effect
.Transitions
.full
758 }, arguments
[1] || { });
760 top
: element
.style
.top
,
761 left
: element
.style
.left
,
762 height
: element
.style
.height
,
763 width
: element
.style
.width
,
764 opacity
: element
.getInlineOpacity() };
766 var dims
= element
.getDimensions();
767 var initialMoveX
, initialMoveY
;
770 switch (options
.direction
) {
772 initialMoveX
= initialMoveY
= moveX
= moveY
= 0;
775 initialMoveX
= dims
.width
;
776 initialMoveY
= moveY
= 0;
780 initialMoveX
= moveX
= 0;
781 initialMoveY
= dims
.height
;
782 moveY
= -dims
.height
;
785 initialMoveX
= dims
.width
;
786 initialMoveY
= dims
.height
;
788 moveY
= -dims
.height
;
791 initialMoveX
= dims
.width
/ 2;
792 initialMoveY
= dims
.height
/ 2;
793 moveX
= -dims
.width
/ 2;
794 moveY
= -dims
.height
/ 2;
798 return new Effect
.Move(element
, {
802 beforeSetup: function(effect
) {
803 effect
.element
.hide().makeClipping().makePositioned();
805 afterFinishInternal: function(effect
) {
807 [ new Effect
.Opacity(effect
.element
, { sync
: true, to
: 1.0, from: 0.0, transition
: options
.opacityTransition
}),
808 new Effect
.Move(effect
.element
, { x
: moveX
, y
: moveY
, sync
: true, transition
: options
.moveTransition
}),
809 new Effect
.Scale(effect
.element
, 100, {
810 scaleMode
: { originalHeight
: dims
.height
, originalWidth
: dims
.width
},
811 sync
: true, scaleFrom
: window
.opera
? 1 : 0, transition
: options
.scaleTransition
, restoreAfterFinish
: true})
813 beforeSetup: function(effect
) {
814 effect
.effects
[0].element
.setStyle({height
: '0px'}).show();
816 afterFinishInternal: function(effect
) {
817 effect
.effects
[0].element
.undoClipping().undoPositioned().setStyle(oldStyle
);
825 Effect
.Shrink = function(element
) {
826 element
= $(element
);
827 var options
= Object
.extend({
829 moveTransition
: Effect
.Transitions
.sinoidal
,
830 scaleTransition
: Effect
.Transitions
.sinoidal
,
831 opacityTransition
: Effect
.Transitions
.none
832 }, arguments
[1] || { });
834 top
: element
.style
.top
,
835 left
: element
.style
.left
,
836 height
: element
.style
.height
,
837 width
: element
.style
.width
,
838 opacity
: element
.getInlineOpacity() };
840 var dims
= element
.getDimensions();
843 switch (options
.direction
) {
860 moveX
= dims
.width
/ 2;
861 moveY
= dims
.height
/ 2;
865 return new Effect
.Parallel(
866 [ new Effect
.Opacity(element
, { sync
: true, to
: 0.0, from: 1.0, transition
: options
.opacityTransition
}),
867 new Effect
.Scale(element
, window
.opera
? 1 : 0, { sync
: true, transition
: options
.scaleTransition
, restoreAfterFinish
: true}),
868 new Effect
.Move(element
, { x
: moveX
, y
: moveY
, sync
: true, transition
: options
.moveTransition
})
870 beforeStartInternal: function(effect
) {
871 effect
.effects
[0].element
.makePositioned().makeClipping();
873 afterFinishInternal: function(effect
) {
874 effect
.effects
[0].element
.hide().undoClipping().undoPositioned().setStyle(oldStyle
); }
879 Effect
.Pulsate = function(element
) {
880 element
= $(element
);
881 var options
= arguments
[1] || { },
882 oldOpacity
= element
.getInlineOpacity(),
883 transition
= options
.transition
|| Effect
.Transitions
.linear
,
884 reverser = function(pos
){
885 return 1 - transition((-Math
.cos((pos
*(options
.pulses
||5)*2)*Math
.PI
)/2) + .5);
888 return new Effect
.Opacity(element
,
889 Object
.extend(Object
.extend({ duration
: 2.0, from: 0,
890 afterFinishInternal: function(effect
) { effect
.element
.setStyle({opacity
: oldOpacity
}); }
891 }, options
), {transition
: reverser
}));
894 Effect
.Fold = function(element
) {
895 element
= $(element
);
897 top
: element
.style
.top
,
898 left
: element
.style
.left
,
899 width
: element
.style
.width
,
900 height
: element
.style
.height
};
901 element
.makeClipping();
902 return new Effect
.Scale(element
, 5, Object
.extend({
905 afterFinishInternal: function(effect
) {
906 new Effect
.Scale(element
, 1, {
909 afterFinishInternal: function(effect
) {
910 effect
.element
.hide().undoClipping().setStyle(oldStyle
);
912 }}, arguments
[1] || { }));
915 Effect
.Morph
= Class
.create(Effect
.Base
, {
916 initialize: function(element
) {
917 this.element
= $(element
);
918 if (!this.element
) throw(Effect
._elementDoesNotExistError
);
919 var options
= Object
.extend({
921 }, arguments
[1] || { });
923 if (!Object
.isString(options
.style
)) this.style
= $H(options
.style
);
925 if (options
.style
.include(':'))
926 this.style
= options
.style
.parseStyle();
928 this.element
.addClassName(options
.style
);
929 this.style
= $H(this.element
.getStyles());
930 this.element
.removeClassName(options
.style
);
931 var css
= this.element
.getStyles();
932 this.style
= this.style
.reject(function(style
) {
933 return style
.value
== css
[style
.key
];
935 options
.afterFinishInternal = function(effect
) {
936 effect
.element
.addClassName(effect
.options
.style
);
937 effect
.transforms
.each(function(transform
) {
938 effect
.element
.style
[transform
.style
] = '';
947 function parseColor(color
){
948 if (!color
|| ['rgba(0, 0, 0, 0)','transparent'].include(color
)) color
= '#ffffff';
949 color
= color
.parseColor();
950 return $R(0,2).map(function(i
){
951 return parseInt( color
.slice(i
*2+1,i
*2+3), 16 );
954 this.transforms
= this.style
.map(function(pair
){
955 var property
= pair
[0], value
= pair
[1], unit
= null;
957 if (value
.parseColor('#zzzzzz') != '#zzzzzz') {
958 value
= value
.parseColor();
960 } else if (property
== 'opacity') {
961 value
= parseFloat(value
);
962 if (Prototype
.Browser
.IE
&& (!this.element
.currentStyle
.hasLayout
))
963 this.element
.setStyle({zoom
: 1});
964 } else if (Element
.CSS_LENGTH
.test(value
)) {
965 var components
= value
.match(/^([\+\-]?[0-9\.]+)(.*)$/);
966 value
= parseFloat(components
[1]);
967 unit
= (components
.length
== 3) ? components
[2] : null;
970 var originalValue
= this.element
.getStyle(property
);
972 style
: property
.camelize(),
973 originalValue
: unit
=='color' ? parseColor(originalValue
) : parseFloat(originalValue
|| 0),
974 targetValue
: unit
=='color' ? parseColor(value
) : value
,
977 }.bind(this)).reject(function(transform
){
979 (transform
.originalValue
== transform
.targetValue
) ||
981 transform
.unit
!= 'color' &&
982 (isNaN(transform
.originalValue
) || isNaN(transform
.targetValue
))
987 update: function(position
) {
988 var style
= { }, transform
, i
= this.transforms
.length
;
990 style
[(transform
= this.transforms
[i
]).style
] =
991 transform
.unit
=='color' ? '#'+
992 (Math
.round(transform
.originalValue
[0]+
993 (transform
.targetValue
[0]-transform
.originalValue
[0])*position
)).toColorPart() +
994 (Math
.round(transform
.originalValue
[1]+
995 (transform
.targetValue
[1]-transform
.originalValue
[1])*position
)).toColorPart() +
996 (Math
.round(transform
.originalValue
[2]+
997 (transform
.targetValue
[2]-transform
.originalValue
[2])*position
)).toColorPart() :
998 (transform
.originalValue
+
999 (transform
.targetValue
- transform
.originalValue
) * position
).toFixed(3) +
1000 (transform
.unit
=== null ? '' : transform
.unit
);
1001 this.element
.setStyle(style
, true);
1005 Effect
.Transform
= Class
.create({
1006 initialize: function(tracks
){
1008 this.options
= arguments
[1] || { };
1009 this.addTracks(tracks
);
1011 addTracks: function(tracks
){
1012 tracks
.each(function(track
){
1014 var data
= track
.values().first();
1015 this.tracks
.push($H({
1016 ids
: track
.keys().first(),
1017 effect
: Effect
.Morph
,
1018 options
: { style
: data
}
1024 return new Effect
.Parallel(
1025 this.tracks
.map(function(track
){
1026 var ids
= track
.get('ids'), effect
= track
.get('effect'), options
= track
.get('options');
1027 var elements
= [$(ids
) || $$(ids
)].flatten();
1028 return elements
.map(function(e
){ return new effect(e
, Object
.extend({ sync
:true }, options
)) });
1035 Element
.CSS_PROPERTIES
= $w(
1036 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' +
1037 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
1038 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
1039 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
1040 'fontSize fontWeight height left letterSpacing lineHeight ' +
1041 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
1042 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
1043 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
1044 'right textIndent top width wordSpacing zIndex');
1046 Element
.CSS_LENGTH
= /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
1048 String
.__parseStyleElement
= document
.createElement('div');
1049 String
.prototype.parseStyle = function(){
1050 var style
, styleRules
= $H();
1051 if (Prototype
.Browser
.WebKit
)
1052 style
= new Element('div',{style
:this}).style
;
1054 String
.__parseStyleElement
.innerHTML
= '<div style="' + this + '"></div>';
1055 style
= String
.__parseStyleElement
.childNodes
[0].style
;
1058 Element
.CSS_PROPERTIES
.each(function(property
){
1059 if (style
[property
]) styleRules
.set(property
, style
[property
]);
1062 if (Prototype
.Browser
.IE
&& this.include('opacity'))
1063 styleRules
.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]);
1068 if (document
.defaultView
&& document
.defaultView
.getComputedStyle
) {
1069 Element
.getStyles = function(element
) {
1070 var css
= document
.defaultView
.getComputedStyle($(element
), null);
1071 return Element
.CSS_PROPERTIES
.inject({ }, function(styles
, property
) {
1072 styles
[property
] = css
[property
];
1077 Element
.getStyles = function(element
) {
1078 element
= $(element
);
1079 var css
= element
.currentStyle
, styles
;
1080 styles
= Element
.CSS_PROPERTIES
.inject({ }, function(results
, property
) {
1081 results
[property
] = css
[property
];
1084 if (!styles
.opacity
) styles
.opacity
= element
.getOpacity();
1090 morph: function(element
, style
) {
1091 element
= $(element
);
1092 new Effect
.Morph(element
, Object
.extend({ style
: style
}, arguments
[2] || { }));
1095 visualEffect: function(element
, effect
, options
) {
1096 element
= $(element
);
1097 var s
= effect
.dasherize().camelize(), klass
= s
.charAt(0).toUpperCase() + s
.substring(1);
1098 new Effect
[klass
](element
, options
);
1101 highlight: function(element
, options
) {
1102 element
= $(element
);
1103 new Effect
.Highlight(element
, options
);
1108 $w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+
1109 'pulsate shake puff squish switchOff dropOut').each(
1111 Effect
.Methods
[effect
] = function(element
, options
){
1112 element
= $(element
);
1113 Effect
[effect
.charAt(0).toUpperCase() + effect
.substring(1)](element
, options
);
1119 $w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each(
1120 function(f
) { Effect
.Methods
[f
] = Element
[f
]; }
1123 Element
.addMethods(Effect
.Methods
);