1 // Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
3 // Justin Palmer (http://encytemedia.com/)
4 // Mark Pilgrim (http://diveintomark.org/)
7 // script.aculo.us is freely distributable under the terms of an MIT-style license.
8 // For details, see the script.aculo.us web site: http://script.aculo.us/
10 // converts rgb() and #xxx to #xxxxxx format,
11 // returns self (or first argument) if not convertable
12 String
.prototype.parseColor = function() {
14 if (this.slice(0,4) == 'rgb(') {
15 var cols
= this.slice(4,this.length
-1).split(',');
16 var i
=0; do { color
+= parseInt(cols
[i
]).toColorPart() } while (++i
<3);
18 if (this.slice(0,1) == '#') {
19 if (this.length
==4) for(var i
=1;i
<4;i
++) color
+= (this.charAt(i
) + this.charAt(i
)).toLowerCase();
20 if (this.length
==7) color
= this.toLowerCase();
23 return (color
.length
==7 ? color
: (arguments
[0] || this));
26 /*--------------------------------------------------------------------------*/
28 Element
.collectTextNodes = function(element
) {
29 return $A($(element
).childNodes
).collect( function(node
) {
30 return (node
.nodeType
==3 ? node
.nodeValue
:
31 (node
.hasChildNodes() ? Element
.collectTextNodes(node
) : ''));
32 }).flatten().join('');
35 Element
.collectTextNodesIgnoreClass = function(element
, className
) {
36 return $A($(element
).childNodes
).collect( function(node
) {
37 return (node
.nodeType
==3 ? node
.nodeValue
:
38 ((node
.hasChildNodes() && !Element
.hasClassName(node
,className
)) ?
39 Element
.collectTextNodesIgnoreClass(node
, className
) : ''));
40 }).flatten().join('');
43 Element
.setContentZoom = function(element
, percent
) {
45 element
.setStyle({fontSize
: (percent
/100) + 'em'});
46 if (Prototype
.Browser
.WebKit
) window
.scrollBy(0,0);
50 Element
.getInlineOpacity = function(element
){
51 return $(element
).style
.opacity
|| '';
54 Element
.forceRerendering = function(element
) {
57 var n
= document
.createTextNode(' ');
58 element
.appendChild(n
);
59 element
.removeChild(n
);
63 /*--------------------------------------------------------------------------*/
66 _elementDoesNotExistError
: {
67 name
: 'ElementDoesNotExistError',
68 message
: 'The specified DOM element does not exist, but is required for this effect to operate'
72 sinoidal: function(pos
) {
73 return (-Math
.cos(pos
*Math
.PI
)/2) + 0.5;
75 reverse: function(pos
) {
78 flicker: function(pos
) {
79 var pos
= ((-Math
.cos(pos
*Math
.PI
)/4) + 0.75) + Math.random()/4;
80 return pos
> 1 ? 1 : pos
;
82 wobble: function(pos
) {
83 return (-Math
.cos(pos
*Math
.PI
*(9*pos
))/2) + 0.5;
85 pulse: function(pos
, pulses
) {
88 ((pos
% (1/pulses
)) * pulses
).round() == 0 ?
89 ((pos
* pulses
* 2) - (pos
* pulses
* 2).floor()) :
90 1 - ((pos
* pulses
* 2) - (pos
* pulses
* 2).floor())
93 spring: function(pos
) {
94 return 1 - (Math
.cos(pos
* 4.5 * Math
.PI
) * Math
.exp(-pos
* 6));
104 duration
: 1.0, // seconds
105 fps
: 100, // 100= assume 66fps max.
106 sync
: false, // true for combining
112 tagifyText: function(element
) {
113 var tagifyStyle
= 'position:relative';
114 if (Prototype
.Browser
.IE
) tagifyStyle
+= ';zoom:1';
116 element
= $(element
);
117 $A(element
.childNodes
).each( function(child
) {
118 if (child
.nodeType
==3) {
119 child
.nodeValue
.toArray().each( function(character
) {
120 element
.insertBefore(
121 new Element('span', {style
: tagifyStyle
}).update(
122 character
== ' ' ? String
.fromCharCode(160) : character
),
125 Element
.remove(child
);
129 multiple: function(element
, effect
) {
131 if (((typeof element
== 'object') ||
132 Object
.isFunction(element
)) &&
136 elements
= $(element
).childNodes
;
138 var options
= Object
.extend({
141 }, arguments
[2] || { });
142 var masterDelay
= options
.delay
;
144 $A(elements
).each( function(element
, index
) {
145 new effect(element
, Object
.extend(options
, { delay
: index
* options
.speed
+ masterDelay
}));
149 'slide': ['SlideDown','SlideUp'],
150 'blind': ['BlindDown','BlindUp'],
151 'appear': ['Appear','Fade']
153 toggle: function(element
, effect
) {
154 element
= $(element
);
155 effect
= (effect
|| 'appear').toLowerCase();
156 var options
= Object
.extend({
157 queue
: { position
:'end', scope
:(element
.id
|| 'global'), limit
: 1 }
158 }, arguments
[2] || { });
159 Effect
[element
.visible() ?
160 Effect
.PAIRS
[effect
][1] : Effect
.PAIRS
[effect
][0]](element
, options
);
164 Effect
.DefaultOptions
.transition
= Effect
.Transitions
.sinoidal
;
166 /* ------------- core effects ------------- */
168 Effect
.ScopedQueue
= Class
.create(Enumerable
, {
169 initialize: function() {
171 this.interval
= null;
173 _each: function(iterator
) {
174 this.effects
._each(iterator
);
176 add: function(effect
) {
177 var timestamp
= new Date().getTime();
179 var position
= Object
.isString(effect
.options
.queue
) ?
180 effect
.options
.queue
: effect
.options
.queue
.position
;
184 // move unstarted effects after this effect
185 this.effects
.findAll(function(e
){ return e
.state
=='idle' }).each( function(e
) {
186 e
.startOn
+= effect
.finishOn
;
187 e
.finishOn
+= effect
.finishOn
;
191 timestamp
= this.effects
.pluck('startOn').max() || timestamp
;
194 // start effect after last queued effect has finished
195 timestamp
= this.effects
.pluck('finishOn').max() || timestamp
;
199 effect
.startOn
+= timestamp
;
200 effect
.finishOn
+= timestamp
;
202 if (!effect
.options
.queue
.limit
|| (this.effects
.length
< effect
.options
.queue
.limit
))
203 this.effects
.push(effect
);
206 this.interval
= setInterval(this.loop
.bind(this), 15);
208 remove: function(effect
) {
209 this.effects
= this.effects
.reject(function(e
) { return e
==effect
});
210 if (this.effects
.length
== 0) {
211 clearInterval(this.interval
);
212 this.interval
= null;
216 var timePos
= new Date().getTime();
217 for(var i
=0, len
=this.effects
.length
;i
<len
;i
++)
218 this.effects
[i
] && this.effects
[i
].loop(timePos
);
224 get: function(queueName
) {
225 if (!Object
.isString(queueName
)) return queueName
;
227 return this.instances
.get(queueName
) ||
228 this.instances
.set(queueName
, new Effect
.ScopedQueue());
231 Effect
.Queue
= Effect
.Queues
.get('global');
233 Effect
.Base
= Class
.create({
235 start: function(options
) {
236 function codeForEvent(options
,eventName
){
238 (options
[eventName
+'Internal'] ? 'this.options.'+eventName
+'Internal(this);' : '') +
239 (options
[eventName
] ? 'this.options.'+eventName
+'(this);' : '')
242 if (options
&& options
.transition
=== false) options
.transition
= Effect
.Transitions
.linear
;
243 this.options
= Object
.extend(Object
.extend({ },Effect
.DefaultOptions
), options
|| { });
244 this.currentFrame
= 0;
246 this.startOn
= this.options
.delay
*1000;
247 this.finishOn
= this.startOn
+(this.options
.duration
*1000);
248 this.fromToDelta
= this.options
.to
-this.options
.from;
249 this.totalTime
= this.finishOn
-this.startOn
;
250 this.totalFrames
= this.options
.fps
*this.options
.duration
;
252 eval('this.render = function(pos){ '+
253 'if (this.state=="idle"){this.state="running";'+
254 codeForEvent(this.options
,'beforeSetup')+
255 (this.setup
? 'this.setup();':'')+
256 codeForEvent(this.options
,'afterSetup')+
257 '};if (this.state=="running"){'+
258 'pos=this.options.transition(pos)*'+this.fromToDelta
+'+'+this.options
.from+';'+
259 'this.position=pos;'+
260 codeForEvent(this.options
,'beforeUpdate')+
261 (this.update
? 'this.update(pos);':'')+
262 codeForEvent(this.options
,'afterUpdate')+
265 this.event('beforeStart');
266 if (!this.options
.sync
)
267 Effect
.Queues
.get(Object
.isString(this.options
.queue
) ?
268 'global' : this.options
.queue
.scope
).add(this);
270 loop: function(timePos
) {
271 if (timePos
>= this.startOn
) {
272 if (timePos
>= this.finishOn
) {
275 this.event('beforeFinish');
276 if (this.finish
) this.finish();
277 this.event('afterFinish');
280 var pos
= (timePos
- this.startOn
) / this.totalTime
,
281 frame
= (pos
* this.totalFrames
).round();
282 if (frame
> this.currentFrame
) {
284 this.currentFrame
= frame
;
289 if (!this.options
.sync
)
290 Effect
.Queues
.get(Object
.isString(this.options
.queue
) ?
291 'global' : this.options
.queue
.scope
).remove(this);
292 this.state
= 'finished';
294 event: function(eventName
) {
295 if (this.options
[eventName
+ 'Internal']) this.options
[eventName
+ 'Internal'](this);
296 if (this.options
[eventName
]) this.options
[eventName
](this);
298 inspect: function() {
300 for(property
in this)
301 if (!Object
.isFunction(this[property
])) data
.set(property
, this[property
]);
302 return '#<Effect:' + data
.inspect() + ',options:' + $H(this.options
).inspect() + '>';
306 Effect
.Parallel
= Class
.create(Effect
.Base
, {
307 initialize: function(effects
) {
308 this.effects
= effects
|| [];
309 this.start(arguments
[1]);
311 update: function(position
) {
312 this.effects
.invoke('render', position
);
314 finish: function(position
) {
315 this.effects
.each( function(effect
) {
318 effect
.event('beforeFinish');
319 if (effect
.finish
) effect
.finish(position
);
320 effect
.event('afterFinish');
325 Effect
.Tween
= Class
.create(Effect
.Base
, {
326 initialize: function(object
, from, to
) {
327 object
= Object
.isString(object
) ? $(object
) : object
;
328 var args
= $A(arguments
), method
= args
.last(),
329 options
= args
.length
== 5 ? args
[3] : null;
330 this.method
= Object
.isFunction(method
) ? method
.bind(object
) :
331 Object
.isFunction(object
[method
]) ? object
[method
].bind(object
) :
332 function(value
) { object
[method
] = value
};
333 this.start(Object
.extend({ from: from, to
: to
}, options
|| { }));
335 update: function(position
) {
336 this.method(position
);
340 Effect
.Event
= Class
.create(Effect
.Base
, {
341 initialize: function() {
342 this.start(Object
.extend({ duration
: 0 }, arguments
[0] || { }));
344 update
: Prototype
.emptyFunction
347 Effect
.Opacity
= Class
.create(Effect
.Base
, {
348 initialize: function(element
) {
349 this.element
= $(element
);
350 if (!this.element
) throw(Effect
._elementDoesNotExistError
);
351 // make this work on IE on elements without 'layout'
352 if (Prototype
.Browser
.IE
&& (!this.element
.currentStyle
.hasLayout
))
353 this.element
.setStyle({zoom
: 1});
354 var options
= Object
.extend({
355 from: this.element
.getOpacity() || 0.0,
357 }, arguments
[1] || { });
360 update: function(position
) {
361 this.element
.setOpacity(position
);
365 Effect
.Move
= Class
.create(Effect
.Base
, {
366 initialize: function(element
) {
367 this.element
= $(element
);
368 if (!this.element
) throw(Effect
._elementDoesNotExistError
);
369 var options
= Object
.extend({
373 }, arguments
[1] || { });
377 this.element
.makePositioned();
378 this.originalLeft
= parseFloat(this.element
.getStyle('left') || '0');
379 this.originalTop
= parseFloat(this.element
.getStyle('top') || '0');
380 if (this.options
.mode
== 'absolute') {
381 this.options
.x
= this.options
.x
- this.originalLeft
;
382 this.options
.y
= this.options
.y
- this.originalTop
;
385 update: function(position
) {
386 this.element
.setStyle({
387 left
: (this.options
.x
* position
+ this.originalLeft
).round() + 'px',
388 top
: (this.options
.y
* position
+ this.originalTop
).round() + 'px'
393 // for backwards compatibility
394 Effect
.MoveBy = function(element
, toTop
, toLeft
) {
395 return new Effect
.Move(element
,
396 Object
.extend({ x
: toLeft
, y
: toTop
}, arguments
[3] || { }));
399 Effect
.Scale
= Class
.create(Effect
.Base
, {
400 initialize: function(element
, percent
) {
401 this.element
= $(element
);
402 if (!this.element
) throw(Effect
._elementDoesNotExistError
);
403 var options
= Object
.extend({
407 scaleFromCenter
: false,
408 scaleMode
: 'box', // 'box' or 'contents' or { } with provided values
411 }, arguments
[2] || { });
415 this.restoreAfterFinish
= this.options
.restoreAfterFinish
|| false;
416 this.elementPositioning
= this.element
.getStyle('position');
418 this.originalStyle
= { };
419 ['top','left','width','height','fontSize'].each( function(k
) {
420 this.originalStyle
[k
] = this.element
.style
[k
];
423 this.originalTop
= this.element
.offsetTop
;
424 this.originalLeft
= this.element
.offsetLeft
;
426 var fontSize
= this.element
.getStyle('font-size') || '100%';
427 ['em','px','%','pt'].each( function(fontSizeType
) {
428 if (fontSize
.indexOf(fontSizeType
)>0) {
429 this.fontSize
= parseFloat(fontSize
);
430 this.fontSizeType
= fontSizeType
;
434 this.factor
= (this.options
.scaleTo
- this.options
.scaleFrom
)/100;
437 if (this.options
.scaleMode
=='box')
438 this.dims
= [this.element
.offsetHeight
, this.element
.offsetWidth
];
439 if (/^content/.test(this.options
.scaleMode
))
440 this.dims
= [this.element
.scrollHeight
, this.element
.scrollWidth
];
442 this.dims
= [this.options
.scaleMode
.originalHeight
,
443 this.options
.scaleMode
.originalWidth
];
445 update: function(position
) {
446 var currentScale
= (this.options
.scaleFrom
/100.0) + (this.factor
* position
);
447 if (this.options
.scaleContent
&& this.fontSize
)
448 this.element
.setStyle({fontSize
: this.fontSize
* currentScale
+ this.fontSizeType
});
449 this.setDimensions(this.dims
[0] * currentScale
, this.dims
[1] * currentScale
);
451 finish: function(position
) {
452 if (this.restoreAfterFinish
) this.element
.setStyle(this.originalStyle
);
454 setDimensions: function(height
, width
) {
456 if (this.options
.scaleX
) d
.width
= width
.round() + 'px';
457 if (this.options
.scaleY
) d
.height
= height
.round() + 'px';
458 if (this.options
.scaleFromCenter
) {
459 var topd
= (height
- this.dims
[0])/2;
460 var leftd
= (width
- this.dims
[1])/2;
461 if (this.elementPositioning
== 'absolute') {
462 if (this.options
.scaleY
) d
.top
= this.originalTop
-topd
+ 'px';
463 if (this.options
.scaleX
) d
.left
= this.originalLeft
-leftd
+ 'px';
465 if (this.options
.scaleY
) d
.top
= -topd
+ 'px';
466 if (this.options
.scaleX
) d
.left
= -leftd
+ 'px';
469 this.element
.setStyle(d
);
473 Effect
.Highlight
= Class
.create(Effect
.Base
, {
474 initialize: function(element
) {
475 this.element
= $(element
);
476 if (!this.element
) throw(Effect
._elementDoesNotExistError
);
477 var options
= Object
.extend({ startcolor
: '#ffff99' }, arguments
[1] || { });
481 // Prevent executing on elements not in the layout flow
482 if (this.element
.getStyle('display')=='none') { this.cancel(); return; }
483 // Disable background image during the effect
485 if (!this.options
.keepBackgroundImage
) {
486 this.oldStyle
.backgroundImage
= this.element
.getStyle('background-image');
487 this.element
.setStyle({backgroundImage
: 'none'});
489 if (!this.options
.endcolor
)
490 this.options
.endcolor
= this.element
.getStyle('background-color').parseColor('#ffffff');
491 if (!this.options
.restorecolor
)
492 this.options
.restorecolor
= this.element
.getStyle('background-color');
493 // init color calculations
494 this._base
= $R(0,2).map(function(i
){ return parseInt(this.options
.startcolor
.slice(i
*2+1,i
*2+3),16) }.bind(this));
495 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));
497 update: function(position
) {
498 this.element
.setStyle({backgroundColor
: $R(0,2).inject('#',function(m
,v
,i
){
499 return m
+((this._base
[i
]+(this._delta
[i
]*position
)).round().toColorPart()); }.bind(this)) });
502 this.element
.setStyle(Object
.extend(this.oldStyle
, {
503 backgroundColor
: this.options
.restorecolor
508 Effect
.ScrollTo = function(element
) {
509 var options
= arguments
[1] || { },
510 scrollOffsets
= document
.viewport
.getScrollOffsets(),
511 elementOffsets
= $(element
).cumulativeOffset(),
512 max
= (window
.height
|| document
.body
.scrollHeight
) - document
.viewport
.getHeight();
514 if (options
.offset
) elementOffsets
[1] += options
.offset
;
516 return new Effect
.Tween(null,
518 elementOffsets
[1] > max
? max
: elementOffsets
[1],
520 function(p
){ scrollTo(scrollOffsets
.left
, p
.round()) }
524 /* ------------- combination effects ------------- */
526 Effect
.Fade = function(element
) {
527 element
= $(element
);
528 var oldOpacity
= element
.getInlineOpacity();
529 var options
= Object
.extend({
530 from: element
.getOpacity() || 1.0,
532 afterFinishInternal: function(effect
) {
533 if (effect
.options
.to
!=0) return;
534 effect
.element
.hide().setStyle({opacity
: oldOpacity
});
536 }, arguments
[1] || { });
537 return new Effect
.Opacity(element
,options
);
540 Effect
.Appear = function(element
) {
541 element
= $(element
);
542 var options
= Object
.extend({
543 from: (element
.getStyle('display') == 'none' ? 0.0 : element
.getOpacity() || 0.0),
545 // force Safari to render floated elements properly
546 afterFinishInternal: function(effect
) {
547 effect
.element
.forceRerendering();
549 beforeSetup: function(effect
) {
550 effect
.element
.setOpacity(effect
.options
.from).show();
551 }}, arguments
[1] || { });
552 return new Effect
.Opacity(element
,options
);
555 Effect
.Puff = function(element
) {
556 element
= $(element
);
558 opacity
: element
.getInlineOpacity(),
559 position
: element
.getStyle('position'),
560 top
: element
.style
.top
,
561 left
: element
.style
.left
,
562 width
: element
.style
.width
,
563 height
: element
.style
.height
565 return new Effect
.Parallel(
566 [ new Effect
.Scale(element
, 200,
567 { sync
: true, scaleFromCenter
: true, scaleContent
: true, restoreAfterFinish
: true }),
568 new Effect
.Opacity(element
, { sync
: true, to
: 0.0 } ) ],
569 Object
.extend({ duration
: 1.0,
570 beforeSetupInternal: function(effect
) {
571 Position
.absolutize(effect
.effects
[0].element
)
573 afterFinishInternal: function(effect
) {
574 effect
.effects
[0].element
.hide().setStyle(oldStyle
); }
575 }, arguments
[1] || { })
579 Effect
.BlindUp = function(element
) {
580 element
= $(element
);
581 element
.makeClipping();
582 return new Effect
.Scale(element
, 0,
583 Object
.extend({ scaleContent
: false,
585 restoreAfterFinish
: true,
586 afterFinishInternal: function(effect
) {
587 effect
.element
.hide().undoClipping();
589 }, arguments
[1] || { })
593 Effect
.BlindDown = function(element
) {
594 element
= $(element
);
595 var elementDimensions
= element
.getDimensions();
596 return new Effect
.Scale(element
, 100, Object
.extend({
600 scaleMode
: {originalHeight
: elementDimensions
.height
, originalWidth
: elementDimensions
.width
},
601 restoreAfterFinish
: true,
602 afterSetup: function(effect
) {
603 effect
.element
.makeClipping().setStyle({height
: '0px'}).show();
605 afterFinishInternal: function(effect
) {
606 effect
.element
.undoClipping();
608 }, arguments
[1] || { }));
611 Effect
.SwitchOff = function(element
) {
612 element
= $(element
);
613 var oldOpacity
= element
.getInlineOpacity();
614 return new Effect
.Appear(element
, Object
.extend({
617 transition
: Effect
.Transitions
.flicker
,
618 afterFinishInternal: function(effect
) {
619 new Effect
.Scale(effect
.element
, 1, {
620 duration
: 0.3, scaleFromCenter
: true,
621 scaleX
: false, scaleContent
: false, restoreAfterFinish
: true,
622 beforeSetup: function(effect
) {
623 effect
.element
.makePositioned().makeClipping();
625 afterFinishInternal: function(effect
) {
626 effect
.element
.hide().undoClipping().undoPositioned().setStyle({opacity
: oldOpacity
});
630 }, arguments
[1] || { }));
633 Effect
.DropOut = function(element
) {
634 element
= $(element
);
636 top
: element
.getStyle('top'),
637 left
: element
.getStyle('left'),
638 opacity
: element
.getInlineOpacity() };
639 return new Effect
.Parallel(
640 [ new Effect
.Move(element
, {x
: 0, y
: 100, sync
: true }),
641 new Effect
.Opacity(element
, { sync
: true, to
: 0.0 }) ],
644 beforeSetup: function(effect
) {
645 effect
.effects
[0].element
.makePositioned();
647 afterFinishInternal: function(effect
) {
648 effect
.effects
[0].element
.hide().undoPositioned().setStyle(oldStyle
);
650 }, arguments
[1] || { }));
653 Effect
.Shake = function(element
) {
654 element
= $(element
);
655 var options
= Object
.extend({
658 }, arguments
[1] || {});
659 var distance
= parseFloat(options
.distance
);
660 var split
= parseFloat(options
.duration
) / 10.0;
662 top
: element
.getStyle('top'),
663 left
: element
.getStyle('left') };
664 return new Effect
.Move(element
,
665 { x
: distance
, y
: 0, duration
: split
, afterFinishInternal: function(effect
) {
666 new Effect
.Move(effect
.element
,
667 { x
: -distance
*2, y
: 0, duration
: split
*2, afterFinishInternal: function(effect
) {
668 new Effect
.Move(effect
.element
,
669 { x
: distance
*2, y
: 0, duration
: split
*2, afterFinishInternal: function(effect
) {
670 new Effect
.Move(effect
.element
,
671 { x
: -distance
*2, y
: 0, duration
: split
*2, afterFinishInternal: function(effect
) {
672 new Effect
.Move(effect
.element
,
673 { x
: distance
*2, y
: 0, duration
: split
*2, afterFinishInternal: function(effect
) {
674 new Effect
.Move(effect
.element
,
675 { x
: -distance
, y
: 0, duration
: split
, afterFinishInternal: function(effect
) {
676 effect
.element
.undoPositioned().setStyle(oldStyle
);
677 }}) }}) }}) }}) }}) }});
680 Effect
.SlideDown = function(element
) {
681 element
= $(element
).cleanWhitespace();
682 // SlideDown need to have the content of the element wrapped in a container element with fixed height!
683 var oldInnerBottom
= element
.down().getStyle('bottom');
684 var elementDimensions
= element
.getDimensions();
685 return new Effect
.Scale(element
, 100, Object
.extend({
688 scaleFrom
: window
.opera
? 0 : 1,
689 scaleMode
: {originalHeight
: elementDimensions
.height
, originalWidth
: elementDimensions
.width
},
690 restoreAfterFinish
: true,
691 afterSetup: function(effect
) {
692 effect
.element
.makePositioned();
693 effect
.element
.down().makePositioned();
694 if (window
.opera
) effect
.element
.setStyle({top
: ''});
695 effect
.element
.makeClipping().setStyle({height
: '0px'}).show();
697 afterUpdateInternal: function(effect
) {
698 effect
.element
.down().setStyle({bottom
:
699 (effect
.dims
[0] - effect
.element
.clientHeight
) + 'px' });
701 afterFinishInternal: function(effect
) {
702 effect
.element
.undoClipping().undoPositioned();
703 effect
.element
.down().undoPositioned().setStyle({bottom
: oldInnerBottom
}); }
704 }, arguments
[1] || { })
708 Effect
.SlideUp = function(element
) {
709 element
= $(element
).cleanWhitespace();
710 var oldInnerBottom
= element
.down().getStyle('bottom');
711 var elementDimensions
= element
.getDimensions();
712 return new Effect
.Scale(element
, window
.opera
? 0 : 1,
713 Object
.extend({ scaleContent
: false,
717 scaleMode
: {originalHeight
: elementDimensions
.height
, originalWidth
: elementDimensions
.width
},
718 restoreAfterFinish
: true,
719 afterSetup: function(effect
) {
720 effect
.element
.makePositioned();
721 effect
.element
.down().makePositioned();
722 if (window
.opera
) effect
.element
.setStyle({top
: ''});
723 effect
.element
.makeClipping().show();
725 afterUpdateInternal: function(effect
) {
726 effect
.element
.down().setStyle({bottom
:
727 (effect
.dims
[0] - effect
.element
.clientHeight
) + 'px' });
729 afterFinishInternal: function(effect
) {
730 effect
.element
.hide().undoClipping().undoPositioned();
731 effect
.element
.down().undoPositioned().setStyle({bottom
: oldInnerBottom
});
733 }, arguments
[1] || { })
737 // Bug in opera makes the TD containing this element expand for a instance after finish
738 Effect
.Squish = function(element
) {
739 return new Effect
.Scale(element
, window
.opera
? 1 : 0, {
740 restoreAfterFinish
: true,
741 beforeSetup: function(effect
) {
742 effect
.element
.makeClipping();
744 afterFinishInternal: function(effect
) {
745 effect
.element
.hide().undoClipping();
750 Effect
.Grow = function(element
) {
751 element
= $(element
);
752 var options
= Object
.extend({
754 moveTransition
: Effect
.Transitions
.sinoidal
,
755 scaleTransition
: Effect
.Transitions
.sinoidal
,
756 opacityTransition
: Effect
.Transitions
.full
757 }, arguments
[1] || { });
759 top
: element
.style
.top
,
760 left
: element
.style
.left
,
761 height
: element
.style
.height
,
762 width
: element
.style
.width
,
763 opacity
: element
.getInlineOpacity() };
765 var dims
= element
.getDimensions();
766 var initialMoveX
, initialMoveY
;
769 switch (options
.direction
) {
771 initialMoveX
= initialMoveY
= moveX
= moveY
= 0;
774 initialMoveX
= dims
.width
;
775 initialMoveY
= moveY
= 0;
779 initialMoveX
= moveX
= 0;
780 initialMoveY
= dims
.height
;
781 moveY
= -dims
.height
;
784 initialMoveX
= dims
.width
;
785 initialMoveY
= dims
.height
;
787 moveY
= -dims
.height
;
790 initialMoveX
= dims
.width
/ 2;
791 initialMoveY
= dims
.height
/ 2;
792 moveX
= -dims
.width
/ 2;
793 moveY
= -dims
.height
/ 2;
797 return new Effect
.Move(element
, {
801 beforeSetup: function(effect
) {
802 effect
.element
.hide().makeClipping().makePositioned();
804 afterFinishInternal: function(effect
) {
806 [ new Effect
.Opacity(effect
.element
, { sync
: true, to
: 1.0, from: 0.0, transition
: options
.opacityTransition
}),
807 new Effect
.Move(effect
.element
, { x
: moveX
, y
: moveY
, sync
: true, transition
: options
.moveTransition
}),
808 new Effect
.Scale(effect
.element
, 100, {
809 scaleMode
: { originalHeight
: dims
.height
, originalWidth
: dims
.width
},
810 sync
: true, scaleFrom
: window
.opera
? 1 : 0, transition
: options
.scaleTransition
, restoreAfterFinish
: true})
812 beforeSetup: function(effect
) {
813 effect
.effects
[0].element
.setStyle({height
: '0px'}).show();
815 afterFinishInternal: function(effect
) {
816 effect
.effects
[0].element
.undoClipping().undoPositioned().setStyle(oldStyle
);
824 Effect
.Shrink = function(element
) {
825 element
= $(element
);
826 var options
= Object
.extend({
828 moveTransition
: Effect
.Transitions
.sinoidal
,
829 scaleTransition
: Effect
.Transitions
.sinoidal
,
830 opacityTransition
: Effect
.Transitions
.none
831 }, arguments
[1] || { });
833 top
: element
.style
.top
,
834 left
: element
.style
.left
,
835 height
: element
.style
.height
,
836 width
: element
.style
.width
,
837 opacity
: element
.getInlineOpacity() };
839 var dims
= element
.getDimensions();
842 switch (options
.direction
) {
859 moveX
= dims
.width
/ 2;
860 moveY
= dims
.height
/ 2;
864 return new Effect
.Parallel(
865 [ new Effect
.Opacity(element
, { sync
: true, to
: 0.0, from: 1.0, transition
: options
.opacityTransition
}),
866 new Effect
.Scale(element
, window
.opera
? 1 : 0, { sync
: true, transition
: options
.scaleTransition
, restoreAfterFinish
: true}),
867 new Effect
.Move(element
, { x
: moveX
, y
: moveY
, sync
: true, transition
: options
.moveTransition
})
869 beforeStartInternal: function(effect
) {
870 effect
.effects
[0].element
.makePositioned().makeClipping();
872 afterFinishInternal: function(effect
) {
873 effect
.effects
[0].element
.hide().undoClipping().undoPositioned().setStyle(oldStyle
); }
878 Effect
.Pulsate = function(element
) {
879 element
= $(element
);
880 var options
= arguments
[1] || { };
881 var oldOpacity
= element
.getInlineOpacity();
882 var transition
= options
.transition
|| Effect
.Transitions
.sinoidal
;
883 var reverser = function(pos
){ return transition(1-Effect
.Transitions
.pulse(pos
, options
.pulses
)) };
884 reverser
.bind(transition
);
885 return new Effect
.Opacity(element
,
886 Object
.extend(Object
.extend({ duration
: 2.0, from: 0,
887 afterFinishInternal: function(effect
) { effect
.element
.setStyle({opacity
: oldOpacity
}); }
888 }, options
), {transition
: reverser
}));
891 Effect
.Fold = function(element
) {
892 element
= $(element
);
894 top
: element
.style
.top
,
895 left
: element
.style
.left
,
896 width
: element
.style
.width
,
897 height
: element
.style
.height
};
898 element
.makeClipping();
899 return new Effect
.Scale(element
, 5, Object
.extend({
902 afterFinishInternal: function(effect
) {
903 new Effect
.Scale(element
, 1, {
906 afterFinishInternal: function(effect
) {
907 effect
.element
.hide().undoClipping().setStyle(oldStyle
);
909 }}, arguments
[1] || { }));
912 Effect
.Morph
= Class
.create(Effect
.Base
, {
913 initialize: function(element
) {
914 this.element
= $(element
);
915 if (!this.element
) throw(Effect
._elementDoesNotExistError
);
916 var options
= Object
.extend({
918 }, arguments
[1] || { });
920 if (!Object
.isString(options
.style
)) this.style
= $H(options
.style
);
922 if (options
.style
.include(':'))
923 this.style
= options
.style
.parseStyle();
925 this.element
.addClassName(options
.style
);
926 this.style
= $H(this.element
.getStyles());
927 this.element
.removeClassName(options
.style
);
928 var css
= this.element
.getStyles();
929 this.style
= this.style
.reject(function(style
) {
930 return style
.value
== css
[style
.key
];
932 options
.afterFinishInternal = function(effect
) {
933 effect
.element
.addClassName(effect
.options
.style
);
934 effect
.transforms
.each(function(transform
) {
935 effect
.element
.style
[transform
.style
] = '';
944 function parseColor(color
){
945 if (!color
|| ['rgba(0, 0, 0, 0)','transparent'].include(color
)) color
= '#ffffff';
946 color
= color
.parseColor();
947 return $R(0,2).map(function(i
){
948 return parseInt( color
.slice(i
*2+1,i
*2+3), 16 )
951 this.transforms
= this.style
.map(function(pair
){
952 var property
= pair
[0], value
= pair
[1], unit
= null;
954 if (value
.parseColor('#zzzzzz') != '#zzzzzz') {
955 value
= value
.parseColor();
957 } else if (property
== 'opacity') {
958 value
= parseFloat(value
);
959 if (Prototype
.Browser
.IE
&& (!this.element
.currentStyle
.hasLayout
))
960 this.element
.setStyle({zoom
: 1});
961 } else if (Element
.CSS_LENGTH
.test(value
)) {
962 var components
= value
.match(/^([\+\-]?[0-9\.]+)(.*)$/);
963 value
= parseFloat(components
[1]);
964 unit
= (components
.length
== 3) ? components
[2] : null;
967 var originalValue
= this.element
.getStyle(property
);
969 style
: property
.camelize(),
970 originalValue
: unit
=='color' ? parseColor(originalValue
) : parseFloat(originalValue
|| 0),
971 targetValue
: unit
=='color' ? parseColor(value
) : value
,
974 }.bind(this)).reject(function(transform
){
976 (transform
.originalValue
== transform
.targetValue
) ||
978 transform
.unit
!= 'color' &&
979 (isNaN(transform
.originalValue
) || isNaN(transform
.targetValue
))
984 update: function(position
) {
985 var style
= { }, transform
, i
= this.transforms
.length
;
987 style
[(transform
= this.transforms
[i
]).style
] =
988 transform
.unit
=='color' ? '#'+
989 (Math
.round(transform
.originalValue
[0]+
990 (transform
.targetValue
[0]-transform
.originalValue
[0])*position
)).toColorPart() +
991 (Math
.round(transform
.originalValue
[1]+
992 (transform
.targetValue
[1]-transform
.originalValue
[1])*position
)).toColorPart() +
993 (Math
.round(transform
.originalValue
[2]+
994 (transform
.targetValue
[2]-transform
.originalValue
[2])*position
)).toColorPart() :
995 (transform
.originalValue
+
996 (transform
.targetValue
- transform
.originalValue
) * position
).toFixed(3) +
997 (transform
.unit
=== null ? '' : transform
.unit
);
998 this.element
.setStyle(style
, true);
1002 Effect
.Transform
= Class
.create({
1003 initialize: function(tracks
){
1005 this.options
= arguments
[1] || { };
1006 this.addTracks(tracks
);
1008 addTracks: function(tracks
){
1009 tracks
.each(function(track
){
1011 var data
= track
.values().first();
1012 this.tracks
.push($H({
1013 ids
: track
.keys().first(),
1014 effect
: Effect
.Morph
,
1015 options
: { style
: data
}
1021 return new Effect
.Parallel(
1022 this.tracks
.map(function(track
){
1023 var ids
= track
.get('ids'), effect
= track
.get('effect'), options
= track
.get('options');
1024 var elements
= [$(ids
) || $$(ids
)].flatten();
1025 return elements
.map(function(e
){ return new effect(e
, Object
.extend({ sync
:true }, options
)) });
1032 Element
.CSS_PROPERTIES
= $w(
1033 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' +
1034 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
1035 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
1036 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
1037 'fontSize fontWeight height left letterSpacing lineHeight ' +
1038 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
1039 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
1040 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
1041 'right textIndent top width wordSpacing zIndex');
1043 Element
.CSS_LENGTH
= /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
1045 String
.__parseStyleElement
= document
.createElement('div');
1046 String
.prototype.parseStyle = function(){
1047 var style
, styleRules
= $H();
1048 if (Prototype
.Browser
.WebKit
)
1049 style
= new Element('div',{style
:this}).style
;
1051 String
.__parseStyleElement
.innerHTML
= '<div style="' + this + '"></div>';
1052 style
= String
.__parseStyleElement
.childNodes
[0].style
;
1055 Element
.CSS_PROPERTIES
.each(function(property
){
1056 if (style
[property
]) styleRules
.set(property
, style
[property
]);
1059 if (Prototype
.Browser
.IE
&& this.include('opacity'))
1060 styleRules
.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]);
1065 if (document
.defaultView
&& document
.defaultView
.getComputedStyle
) {
1066 Element
.getStyles = function(element
) {
1067 var css
= document
.defaultView
.getComputedStyle($(element
), null);
1068 return Element
.CSS_PROPERTIES
.inject({ }, function(styles
, property
) {
1069 styles
[property
] = css
[property
];
1074 Element
.getStyles = function(element
) {
1075 element
= $(element
);
1076 var css
= element
.currentStyle
, styles
;
1077 styles
= Element
.CSS_PROPERTIES
.inject({ }, function(hash
, property
) {
1078 hash
.set(property
, css
[property
]);
1081 if (!styles
.opacity
) styles
.set('opacity', element
.getOpacity());
1087 morph: function(element
, style
) {
1088 element
= $(element
);
1089 new Effect
.Morph(element
, Object
.extend({ style
: style
}, arguments
[2] || { }));
1092 visualEffect: function(element
, effect
, options
) {
1093 element
= $(element
)
1094 var s
= effect
.dasherize().camelize(), klass
= s
.charAt(0).toUpperCase() + s
.substring(1);
1095 new Effect
[klass
](element
, options
);
1098 highlight: function(element
, options
) {
1099 element
= $(element
);
1100 new Effect
.Highlight(element
, options
);
1105 $w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+
1106 'pulsate shake puff squish switchOff dropOut').each(
1108 Effect
.Methods
[effect
] = function(element
, options
){
1109 element
= $(element
);
1110 Effect
[effect
.charAt(0).toUpperCase() + effect
.substring(1)](element
, options
);
1116 $w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each(
1117 function(f
) { Effect
.Methods
[f
] = Element
[f
]; }
1120 Element
.addMethods(Effect
.Methods
);