1 // Copyright (c) 2005-2008 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) + .5;
75 reverse: function(pos
) {
78 flicker: function(pos
) {
79 var pos
= ((-Math
.cos(pos
*Math
.PI
)/4) + .75) + Math.random()/4;
80 return pos
> 1 ? 1 : pos
;
82 wobble: function(pos
) {
83 return (-Math
.cos(pos
*Math
.PI
*(9*pos
))/2) + .5;
85 pulse: function(pos
, pulses
) {
86 return (-Math
.cos((pos
*((pulses
||5)-.5)*2)*Math
.PI
)/2) + .5;
88 spring: function(pos
) {
89 return 1 - (Math
.cos(pos
* 4.5 * Math
.PI
) * Math
.exp(-pos
* 6));
99 duration
: 1.0, // seconds
100 fps
: 100, // 100= assume 66fps max.
101 sync
: false, // true for combining
107 tagifyText: function(element
) {
108 var tagifyStyle
= 'position:relative';
109 if (Prototype
.Browser
.IE
) tagifyStyle
+= ';zoom:1';
111 element
= $(element
);
112 $A(element
.childNodes
).each( function(child
) {
113 if (child
.nodeType
==3) {
114 child
.nodeValue
.toArray().each( function(character
) {
115 element
.insertBefore(
116 new Element('span', {style
: tagifyStyle
}).update(
117 character
== ' ' ? String
.fromCharCode(160) : character
),
120 Element
.remove(child
);
124 multiple: function(element
, effect
) {
126 if (((typeof element
== 'object') ||
127 Object
.isFunction(element
)) &&
131 elements
= $(element
).childNodes
;
133 var options
= Object
.extend({
136 }, arguments
[2] || { });
137 var masterDelay
= options
.delay
;
139 $A(elements
).each( function(element
, index
) {
140 new effect(element
, Object
.extend(options
, { delay
: index
* options
.speed
+ masterDelay
}));
144 'slide': ['SlideDown','SlideUp'],
145 'blind': ['BlindDown','BlindUp'],
146 'appear': ['Appear','Fade']
148 toggle: function(element
, effect
) {
149 element
= $(element
);
150 effect
= (effect
|| 'appear').toLowerCase();
151 var options
= Object
.extend({
152 queue
: { position
:'end', scope
:(element
.id
|| 'global'), limit
: 1 }
153 }, arguments
[2] || { });
154 Effect
[element
.visible() ?
155 Effect
.PAIRS
[effect
][1] : Effect
.PAIRS
[effect
][0]](element
, options
);
159 Effect
.DefaultOptions
.transition
= Effect
.Transitions
.sinoidal
;
161 /* ------------- core effects ------------- */
163 Effect
.ScopedQueue
= Class
.create(Enumerable
, {
164 initialize: function() {
166 this.interval
= null;
168 _each: function(iterator
) {
169 this.effects
._each(iterator
);
171 add: function(effect
) {
172 var timestamp
= new Date().getTime();
174 var position
= Object
.isString(effect
.options
.queue
) ?
175 effect
.options
.queue
: effect
.options
.queue
.position
;
179 // move unstarted effects after this effect
180 this.effects
.findAll(function(e
){ return e
.state
=='idle' }).each( function(e
) {
181 e
.startOn
+= effect
.finishOn
;
182 e
.finishOn
+= effect
.finishOn
;
186 timestamp
= this.effects
.pluck('startOn').max() || timestamp
;
189 // start effect after last queued effect has finished
190 timestamp
= this.effects
.pluck('finishOn').max() || timestamp
;
194 effect
.startOn
+= timestamp
;
195 effect
.finishOn
+= timestamp
;
197 if (!effect
.options
.queue
.limit
|| (this.effects
.length
< effect
.options
.queue
.limit
))
198 this.effects
.push(effect
);
201 this.interval
= setInterval(this.loop
.bind(this), 15);
203 remove: function(effect
) {
204 this.effects
= this.effects
.reject(function(e
) { return e
==effect
});
205 if (this.effects
.length
== 0) {
206 clearInterval(this.interval
);
207 this.interval
= null;
211 var timePos
= new Date().getTime();
212 for(var i
=0, len
=this.effects
.length
;i
<len
;i
++)
213 this.effects
[i
] && this.effects
[i
].loop(timePos
);
219 get: function(queueName
) {
220 if (!Object
.isString(queueName
)) return queueName
;
222 return this.instances
.get(queueName
) ||
223 this.instances
.set(queueName
, new Effect
.ScopedQueue());
226 Effect
.Queue
= Effect
.Queues
.get('global');
228 Effect
.Base
= Class
.create({
230 start: function(options
) {
231 function codeForEvent(options
,eventName
){
233 (options
[eventName
+'Internal'] ? 'this.options.'+eventName
+'Internal(this);' : '') +
234 (options
[eventName
] ? 'this.options.'+eventName
+'(this);' : '')
237 if (options
&& options
.transition
=== false) options
.transition
= Effect
.Transitions
.linear
;
238 this.options
= Object
.extend(Object
.extend({ },Effect
.DefaultOptions
), options
|| { });
239 this.currentFrame
= 0;
241 this.startOn
= this.options
.delay
*1000;
242 this.finishOn
= this.startOn
+(this.options
.duration
*1000);
243 this.fromToDelta
= this.options
.to
-this.options
.from;
244 this.totalTime
= this.finishOn
-this.startOn
;
245 this.totalFrames
= this.options
.fps
*this.options
.duration
;
247 this.render
= (function() {
248 function dispatch(effect
, eventName
) {
249 if (effect
.options
[eventName
+ 'Internal'])
250 effect
.options
[eventName
+ 'Internal'](effect
);
251 if (effect
.options
[eventName
])
252 effect
.options
[eventName
](effect
);
255 return function(pos
) {
256 if (this.state
=== "idle") {
257 this.state
= "running";
258 dispatch(this, 'beforeSetup');
259 if (this.setup
) this.setup();
260 dispatch(this, 'afterSetup');
262 if (this.state
=== "running") {
263 pos
= (this.options
.transition(pos
) * this.fromToDelta
) + this.options
.from;
265 dispatch(this, 'beforeUpdate');
266 if (this.update
) this.update(pos
);
267 dispatch(this, 'afterUpdate');
272 this.event('beforeStart');
273 if (!this.options
.sync
)
274 Effect
.Queues
.get(Object
.isString(this.options
.queue
) ?
275 'global' : this.options
.queue
.scope
).add(this);
277 loop: function(timePos
) {
278 if (timePos
>= this.startOn
) {
279 if (timePos
>= this.finishOn
) {
282 this.event('beforeFinish');
283 if (this.finish
) this.finish();
284 this.event('afterFinish');
287 var pos
= (timePos
- this.startOn
) / this.totalTime
,
288 frame
= (pos
* this.totalFrames
).round();
289 if (frame
> this.currentFrame
) {
291 this.currentFrame
= frame
;
296 if (!this.options
.sync
)
297 Effect
.Queues
.get(Object
.isString(this.options
.queue
) ?
298 'global' : this.options
.queue
.scope
).remove(this);
299 this.state
= 'finished';
301 event: function(eventName
) {
302 if (this.options
[eventName
+ 'Internal']) this.options
[eventName
+ 'Internal'](this);
303 if (this.options
[eventName
]) this.options
[eventName
](this);
305 inspect: function() {
307 for(property
in this)
308 if (!Object
.isFunction(this[property
])) data
.set(property
, this[property
]);
309 return '#<Effect:' + data
.inspect() + ',options:' + $H(this.options
).inspect() + '>';
313 Effect
.Parallel
= Class
.create(Effect
.Base
, {
314 initialize: function(effects
) {
315 this.effects
= effects
|| [];
316 this.start(arguments
[1]);
318 update: function(position
) {
319 this.effects
.invoke('render', position
);
321 finish: function(position
) {
322 this.effects
.each( function(effect
) {
325 effect
.event('beforeFinish');
326 if (effect
.finish
) effect
.finish(position
);
327 effect
.event('afterFinish');
332 Effect
.Tween
= Class
.create(Effect
.Base
, {
333 initialize: function(object
, from, to
) {
334 object
= Object
.isString(object
) ? $(object
) : object
;
335 var args
= $A(arguments
), method
= args
.last(),
336 options
= args
.length
== 5 ? args
[3] : null;
337 this.method
= Object
.isFunction(method
) ? method
.bind(object
) :
338 Object
.isFunction(object
[method
]) ? object
[method
].bind(object
) :
339 function(value
) { object
[method
] = value
};
340 this.start(Object
.extend({ from: from, to
: to
}, options
|| { }));
342 update: function(position
) {
343 this.method(position
);
347 Effect
.Event
= Class
.create(Effect
.Base
, {
348 initialize: function() {
349 this.start(Object
.extend({ duration
: 0 }, arguments
[0] || { }));
351 update
: Prototype
.emptyFunction
354 Effect
.Opacity
= Class
.create(Effect
.Base
, {
355 initialize: function(element
) {
356 this.element
= $(element
);
357 if (!this.element
) throw(Effect
._elementDoesNotExistError
);
358 // make this work on IE on elements without 'layout'
359 if (Prototype
.Browser
.IE
&& (!this.element
.currentStyle
.hasLayout
))
360 this.element
.setStyle({zoom
: 1});
361 var options
= Object
.extend({
362 from: this.element
.getOpacity() || 0.0,
364 }, arguments
[1] || { });
367 update: function(position
) {
368 this.element
.setOpacity(position
);
372 Effect
.Move
= Class
.create(Effect
.Base
, {
373 initialize: function(element
) {
374 this.element
= $(element
);
375 if (!this.element
) throw(Effect
._elementDoesNotExistError
);
376 var options
= Object
.extend({
380 }, arguments
[1] || { });
384 this.element
.makePositioned();
385 this.originalLeft
= parseFloat(this.element
.getStyle('left') || '0');
386 this.originalTop
= parseFloat(this.element
.getStyle('top') || '0');
387 if (this.options
.mode
== 'absolute') {
388 this.options
.x
= this.options
.x
- this.originalLeft
;
389 this.options
.y
= this.options
.y
- this.originalTop
;
392 update: function(position
) {
393 this.element
.setStyle({
394 left
: (this.options
.x
* position
+ this.originalLeft
).round() + 'px',
395 top
: (this.options
.y
* position
+ this.originalTop
).round() + 'px'
400 // for backwards compatibility
401 Effect
.MoveBy = function(element
, toTop
, toLeft
) {
402 return new Effect
.Move(element
,
403 Object
.extend({ x
: toLeft
, y
: toTop
}, arguments
[3] || { }));
406 Effect
.Scale
= Class
.create(Effect
.Base
, {
407 initialize: function(element
, percent
) {
408 this.element
= $(element
);
409 if (!this.element
) throw(Effect
._elementDoesNotExistError
);
410 var options
= Object
.extend({
414 scaleFromCenter
: false,
415 scaleMode
: 'box', // 'box' or 'contents' or { } with provided values
418 }, arguments
[2] || { });
422 this.restoreAfterFinish
= this.options
.restoreAfterFinish
|| false;
423 this.elementPositioning
= this.element
.getStyle('position');
425 this.originalStyle
= { };
426 ['top','left','width','height','fontSize'].each( function(k
) {
427 this.originalStyle
[k
] = this.element
.style
[k
];
430 this.originalTop
= this.element
.offsetTop
;
431 this.originalLeft
= this.element
.offsetLeft
;
433 var fontSize
= this.element
.getStyle('font-size') || '100%';
434 ['em','px','%','pt'].each( function(fontSizeType
) {
435 if (fontSize
.indexOf(fontSizeType
)>0) {
436 this.fontSize
= parseFloat(fontSize
);
437 this.fontSizeType
= fontSizeType
;
441 this.factor
= (this.options
.scaleTo
- this.options
.scaleFrom
)/100;
444 if (this.options
.scaleMode
=='box')
445 this.dims
= [this.element
.offsetHeight
, this.element
.offsetWidth
];
446 if (/^content/.test(this.options
.scaleMode
))
447 this.dims
= [this.element
.scrollHeight
, this.element
.scrollWidth
];
449 this.dims
= [this.options
.scaleMode
.originalHeight
,
450 this.options
.scaleMode
.originalWidth
];
452 update: function(position
) {
453 var currentScale
= (this.options
.scaleFrom
/100.0) + (this.factor
* position
);
454 if (this.options
.scaleContent
&& this.fontSize
)
455 this.element
.setStyle({fontSize
: this.fontSize
* currentScale
+ this.fontSizeType
});
456 this.setDimensions(this.dims
[0] * currentScale
, this.dims
[1] * currentScale
);
458 finish: function(position
) {
459 if (this.restoreAfterFinish
) this.element
.setStyle(this.originalStyle
);
461 setDimensions: function(height
, width
) {
463 if (this.options
.scaleX
) d
.width
= width
.round() + 'px';
464 if (this.options
.scaleY
) d
.height
= height
.round() + 'px';
465 if (this.options
.scaleFromCenter
) {
466 var topd
= (height
- this.dims
[0])/2;
467 var leftd
= (width
- this.dims
[1])/2;
468 if (this.elementPositioning
== 'absolute') {
469 if (this.options
.scaleY
) d
.top
= this.originalTop
-topd
+ 'px';
470 if (this.options
.scaleX
) d
.left
= this.originalLeft
-leftd
+ 'px';
472 if (this.options
.scaleY
) d
.top
= -topd
+ 'px';
473 if (this.options
.scaleX
) d
.left
= -leftd
+ 'px';
476 this.element
.setStyle(d
);
480 Effect
.Highlight
= Class
.create(Effect
.Base
, {
481 initialize: function(element
) {
482 this.element
= $(element
);
483 if (!this.element
) throw(Effect
._elementDoesNotExistError
);
484 var options
= Object
.extend({ startcolor
: '#ffff99' }, arguments
[1] || { });
488 // Prevent executing on elements not in the layout flow
489 if (this.element
.getStyle('display')=='none') { this.cancel(); return; }
490 // Disable background image during the effect
492 if (!this.options
.keepBackgroundImage
) {
493 this.oldStyle
.backgroundImage
= this.element
.getStyle('background-image');
494 this.element
.setStyle({backgroundImage
: 'none'});
496 if (!this.options
.endcolor
)
497 this.options
.endcolor
= this.element
.getStyle('background-color').parseColor('#ffffff');
498 if (!this.options
.restorecolor
)
499 this.options
.restorecolor
= this.element
.getStyle('background-color');
500 // init color calculations
501 this._base
= $R(0,2).map(function(i
){ return parseInt(this.options
.startcolor
.slice(i
*2+1,i
*2+3),16) }.bind(this));
502 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));
504 update: function(position
) {
505 this.element
.setStyle({backgroundColor
: $R(0,2).inject('#',function(m
,v
,i
){
506 return m
+((this._base
[i
]+(this._delta
[i
]*position
)).round().toColorPart()); }.bind(this)) });
509 this.element
.setStyle(Object
.extend(this.oldStyle
, {
510 backgroundColor
: this.options
.restorecolor
515 Effect
.ScrollTo = function(element
) {
516 var options
= arguments
[1] || { },
517 scrollOffsets
= document
.viewport
.getScrollOffsets(),
518 elementOffsets
= $(element
).cumulativeOffset();
520 if (options
.offset
) elementOffsets
[1] += options
.offset
;
522 return new Effect
.Tween(null,
526 function(p
){ scrollTo(scrollOffsets
.left
, p
.round()); }
530 /* ------------- combination effects ------------- */
532 Effect
.Fade = function(element
) {
533 element
= $(element
);
534 var oldOpacity
= element
.getInlineOpacity();
535 var options
= Object
.extend({
536 from: element
.getOpacity() || 1.0,
538 afterFinishInternal: function(effect
) {
539 if (effect
.options
.to
!=0) return;
540 effect
.element
.hide().setStyle({opacity
: oldOpacity
});
542 }, arguments
[1] || { });
543 return new Effect
.Opacity(element
,options
);
546 Effect
.Appear = function(element
) {
547 element
= $(element
);
548 var options
= Object
.extend({
549 from: (element
.getStyle('display') == 'none' ? 0.0 : element
.getOpacity() || 0.0),
551 // force Safari to render floated elements properly
552 afterFinishInternal: function(effect
) {
553 effect
.element
.forceRerendering();
555 beforeSetup: function(effect
) {
556 effect
.element
.setOpacity(effect
.options
.from).show();
557 }}, arguments
[1] || { });
558 return new Effect
.Opacity(element
,options
);
561 Effect
.Puff = function(element
) {
562 element
= $(element
);
564 opacity
: element
.getInlineOpacity(),
565 position
: element
.getStyle('position'),
566 top
: element
.style
.top
,
567 left
: element
.style
.left
,
568 width
: element
.style
.width
,
569 height
: element
.style
.height
571 return new Effect
.Parallel(
572 [ new Effect
.Scale(element
, 200,
573 { sync
: true, scaleFromCenter
: true, scaleContent
: true, restoreAfterFinish
: true }),
574 new Effect
.Opacity(element
, { sync
: true, to
: 0.0 } ) ],
575 Object
.extend({ duration
: 1.0,
576 beforeSetupInternal: function(effect
) {
577 Position
.absolutize(effect
.effects
[0].element
);
579 afterFinishInternal: function(effect
) {
580 effect
.effects
[0].element
.hide().setStyle(oldStyle
); }
581 }, arguments
[1] || { })
585 Effect
.BlindUp = function(element
) {
586 element
= $(element
);
587 element
.makeClipping();
588 return new Effect
.Scale(element
, 0,
589 Object
.extend({ scaleContent
: false,
591 restoreAfterFinish
: true,
592 afterFinishInternal: function(effect
) {
593 effect
.element
.hide().undoClipping();
595 }, arguments
[1] || { })
599 Effect
.BlindDown = function(element
) {
600 element
= $(element
);
601 var elementDimensions
= element
.getDimensions();
602 return new Effect
.Scale(element
, 100, Object
.extend({
606 scaleMode
: {originalHeight
: elementDimensions
.height
, originalWidth
: elementDimensions
.width
},
607 restoreAfterFinish
: true,
608 afterSetup: function(effect
) {
609 effect
.element
.makeClipping().setStyle({height
: '0px'}).show();
611 afterFinishInternal: function(effect
) {
612 effect
.element
.undoClipping();
614 }, arguments
[1] || { }));
617 Effect
.SwitchOff = function(element
) {
618 element
= $(element
);
619 var oldOpacity
= element
.getInlineOpacity();
620 return new Effect
.Appear(element
, Object
.extend({
623 transition
: Effect
.Transitions
.flicker
,
624 afterFinishInternal: function(effect
) {
625 new Effect
.Scale(effect
.element
, 1, {
626 duration
: 0.3, scaleFromCenter
: true,
627 scaleX
: false, scaleContent
: false, restoreAfterFinish
: true,
628 beforeSetup: function(effect
) {
629 effect
.element
.makePositioned().makeClipping();
631 afterFinishInternal: function(effect
) {
632 effect
.element
.hide().undoClipping().undoPositioned().setStyle({opacity
: oldOpacity
});
636 }, arguments
[1] || { }));
639 Effect
.DropOut = function(element
) {
640 element
= $(element
);
642 top
: element
.getStyle('top'),
643 left
: element
.getStyle('left'),
644 opacity
: element
.getInlineOpacity() };
645 return new Effect
.Parallel(
646 [ new Effect
.Move(element
, {x
: 0, y
: 100, sync
: true }),
647 new Effect
.Opacity(element
, { sync
: true, to
: 0.0 }) ],
650 beforeSetup: function(effect
) {
651 effect
.effects
[0].element
.makePositioned();
653 afterFinishInternal: function(effect
) {
654 effect
.effects
[0].element
.hide().undoPositioned().setStyle(oldStyle
);
656 }, arguments
[1] || { }));
659 Effect
.Shake = function(element
) {
660 element
= $(element
);
661 var options
= Object
.extend({
664 }, arguments
[1] || {});
665 var distance
= parseFloat(options
.distance
);
666 var split
= parseFloat(options
.duration
) / 10.0;
668 top
: element
.getStyle('top'),
669 left
: element
.getStyle('left') };
670 return new Effect
.Move(element
,
671 { x
: distance
, y
: 0, duration
: split
, 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
*2, y
: 0, duration
: split
*2, afterFinishInternal: function(effect
) {
676 new Effect
.Move(effect
.element
,
677 { x
: -distance
*2, y
: 0, duration
: split
*2, afterFinishInternal: function(effect
) {
678 new Effect
.Move(effect
.element
,
679 { x
: distance
*2, y
: 0, duration
: split
*2, afterFinishInternal: function(effect
) {
680 new Effect
.Move(effect
.element
,
681 { x
: -distance
, y
: 0, duration
: split
, afterFinishInternal: function(effect
) {
682 effect
.element
.undoPositioned().setStyle(oldStyle
);
683 }}); }}); }}); }}); }}); }});
686 Effect
.SlideDown = function(element
) {
687 element
= $(element
).cleanWhitespace();
688 // SlideDown need to have the content of the element wrapped in a container element with fixed height!
689 var oldInnerBottom
= element
.down().getStyle('bottom');
690 var elementDimensions
= element
.getDimensions();
691 return new Effect
.Scale(element
, 100, Object
.extend({
694 scaleFrom
: window
.opera
? 0 : 1,
695 scaleMode
: {originalHeight
: elementDimensions
.height
, originalWidth
: elementDimensions
.width
},
696 restoreAfterFinish
: true,
697 afterSetup: function(effect
) {
698 effect
.element
.makePositioned();
699 effect
.element
.down().makePositioned();
700 if (window
.opera
) effect
.element
.setStyle({top
: ''});
701 effect
.element
.makeClipping().setStyle({height
: '0px'}).show();
703 afterUpdateInternal: function(effect
) {
704 effect
.element
.down().setStyle({bottom
:
705 (effect
.dims
[0] - effect
.element
.clientHeight
) + 'px' });
707 afterFinishInternal: function(effect
) {
708 effect
.element
.undoClipping().undoPositioned();
709 effect
.element
.down().undoPositioned().setStyle({bottom
: oldInnerBottom
}); }
710 }, arguments
[1] || { })
714 Effect
.SlideUp = function(element
) {
715 element
= $(element
).cleanWhitespace();
716 var oldInnerBottom
= element
.down().getStyle('bottom');
717 var elementDimensions
= element
.getDimensions();
718 return new Effect
.Scale(element
, window
.opera
? 0 : 1,
719 Object
.extend({ scaleContent
: false,
723 scaleMode
: {originalHeight
: elementDimensions
.height
, originalWidth
: elementDimensions
.width
},
724 restoreAfterFinish
: true,
725 afterSetup: function(effect
) {
726 effect
.element
.makePositioned();
727 effect
.element
.down().makePositioned();
728 if (window
.opera
) effect
.element
.setStyle({top
: ''});
729 effect
.element
.makeClipping().show();
731 afterUpdateInternal: function(effect
) {
732 effect
.element
.down().setStyle({bottom
:
733 (effect
.dims
[0] - effect
.element
.clientHeight
) + 'px' });
735 afterFinishInternal: function(effect
) {
736 effect
.element
.hide().undoClipping().undoPositioned();
737 effect
.element
.down().undoPositioned().setStyle({bottom
: oldInnerBottom
});
739 }, arguments
[1] || { })
743 // Bug in opera makes the TD containing this element expand for a instance after finish
744 Effect
.Squish = function(element
) {
745 return new Effect
.Scale(element
, window
.opera
? 1 : 0, {
746 restoreAfterFinish
: true,
747 beforeSetup: function(effect
) {
748 effect
.element
.makeClipping();
750 afterFinishInternal: function(effect
) {
751 effect
.element
.hide().undoClipping();
756 Effect
.Grow = function(element
) {
757 element
= $(element
);
758 var options
= Object
.extend({
760 moveTransition
: Effect
.Transitions
.sinoidal
,
761 scaleTransition
: Effect
.Transitions
.sinoidal
,
762 opacityTransition
: Effect
.Transitions
.full
763 }, arguments
[1] || { });
765 top
: element
.style
.top
,
766 left
: element
.style
.left
,
767 height
: element
.style
.height
,
768 width
: element
.style
.width
,
769 opacity
: element
.getInlineOpacity() };
771 var dims
= element
.getDimensions();
772 var initialMoveX
, initialMoveY
;
775 switch (options
.direction
) {
777 initialMoveX
= initialMoveY
= moveX
= moveY
= 0;
780 initialMoveX
= dims
.width
;
781 initialMoveY
= moveY
= 0;
785 initialMoveX
= moveX
= 0;
786 initialMoveY
= dims
.height
;
787 moveY
= -dims
.height
;
790 initialMoveX
= dims
.width
;
791 initialMoveY
= dims
.height
;
793 moveY
= -dims
.height
;
796 initialMoveX
= dims
.width
/ 2;
797 initialMoveY
= dims
.height
/ 2;
798 moveX
= -dims
.width
/ 2;
799 moveY
= -dims
.height
/ 2;
803 return new Effect
.Move(element
, {
807 beforeSetup: function(effect
) {
808 effect
.element
.hide().makeClipping().makePositioned();
810 afterFinishInternal: function(effect
) {
812 [ new Effect
.Opacity(effect
.element
, { sync
: true, to
: 1.0, from: 0.0, transition
: options
.opacityTransition
}),
813 new Effect
.Move(effect
.element
, { x
: moveX
, y
: moveY
, sync
: true, transition
: options
.moveTransition
}),
814 new Effect
.Scale(effect
.element
, 100, {
815 scaleMode
: { originalHeight
: dims
.height
, originalWidth
: dims
.width
},
816 sync
: true, scaleFrom
: window
.opera
? 1 : 0, transition
: options
.scaleTransition
, restoreAfterFinish
: true})
818 beforeSetup: function(effect
) {
819 effect
.effects
[0].element
.setStyle({height
: '0px'}).show();
821 afterFinishInternal: function(effect
) {
822 effect
.effects
[0].element
.undoClipping().undoPositioned().setStyle(oldStyle
);
830 Effect
.Shrink = function(element
) {
831 element
= $(element
);
832 var options
= Object
.extend({
834 moveTransition
: Effect
.Transitions
.sinoidal
,
835 scaleTransition
: Effect
.Transitions
.sinoidal
,
836 opacityTransition
: Effect
.Transitions
.none
837 }, arguments
[1] || { });
839 top
: element
.style
.top
,
840 left
: element
.style
.left
,
841 height
: element
.style
.height
,
842 width
: element
.style
.width
,
843 opacity
: element
.getInlineOpacity() };
845 var dims
= element
.getDimensions();
848 switch (options
.direction
) {
865 moveX
= dims
.width
/ 2;
866 moveY
= dims
.height
/ 2;
870 return new Effect
.Parallel(
871 [ new Effect
.Opacity(element
, { sync
: true, to
: 0.0, from: 1.0, transition
: options
.opacityTransition
}),
872 new Effect
.Scale(element
, window
.opera
? 1 : 0, { sync
: true, transition
: options
.scaleTransition
, restoreAfterFinish
: true}),
873 new Effect
.Move(element
, { x
: moveX
, y
: moveY
, sync
: true, transition
: options
.moveTransition
})
875 beforeStartInternal: function(effect
) {
876 effect
.effects
[0].element
.makePositioned().makeClipping();
878 afterFinishInternal: function(effect
) {
879 effect
.effects
[0].element
.hide().undoClipping().undoPositioned().setStyle(oldStyle
); }
884 Effect
.Pulsate = function(element
) {
885 element
= $(element
);
886 var options
= arguments
[1] || { },
887 oldOpacity
= element
.getInlineOpacity(),
888 transition
= options
.transition
|| Effect
.Transitions
.linear
,
889 reverser = function(pos
){
890 return 1 - transition((-Math
.cos((pos
*(options
.pulses
||5)*2)*Math
.PI
)/2) + .5);
893 return new Effect
.Opacity(element
,
894 Object
.extend(Object
.extend({ duration
: 2.0, from: 0,
895 afterFinishInternal: function(effect
) { effect
.element
.setStyle({opacity
: oldOpacity
}); }
896 }, options
), {transition
: reverser
}));
899 Effect
.Fold = function(element
) {
900 element
= $(element
);
902 top
: element
.style
.top
,
903 left
: element
.style
.left
,
904 width
: element
.style
.width
,
905 height
: element
.style
.height
};
906 element
.makeClipping();
907 return new Effect
.Scale(element
, 5, Object
.extend({
910 afterFinishInternal: function(effect
) {
911 new Effect
.Scale(element
, 1, {
914 afterFinishInternal: function(effect
) {
915 effect
.element
.hide().undoClipping().setStyle(oldStyle
);
917 }}, arguments
[1] || { }));
920 Effect
.Morph
= Class
.create(Effect
.Base
, {
921 initialize: function(element
) {
922 this.element
= $(element
);
923 if (!this.element
) throw(Effect
._elementDoesNotExistError
);
924 var options
= Object
.extend({
926 }, arguments
[1] || { });
928 if (!Object
.isString(options
.style
)) this.style
= $H(options
.style
);
930 if (options
.style
.include(':'))
931 this.style
= options
.style
.parseStyle();
933 this.element
.addClassName(options
.style
);
934 this.style
= $H(this.element
.getStyles());
935 this.element
.removeClassName(options
.style
);
936 var css
= this.element
.getStyles();
937 this.style
= this.style
.reject(function(style
) {
938 return style
.value
== css
[style
.key
];
940 options
.afterFinishInternal = function(effect
) {
941 effect
.element
.addClassName(effect
.options
.style
);
942 effect
.transforms
.each(function(transform
) {
943 effect
.element
.style
[transform
.style
] = '';
952 function parseColor(color
){
953 if (!color
|| ['rgba(0, 0, 0, 0)','transparent'].include(color
)) color
= '#ffffff';
954 color
= color
.parseColor();
955 return $R(0,2).map(function(i
){
956 return parseInt( color
.slice(i
*2+1,i
*2+3), 16 );
959 this.transforms
= this.style
.map(function(pair
){
960 var property
= pair
[0], value
= pair
[1], unit
= null;
962 if (value
.parseColor('#zzzzzz') != '#zzzzzz') {
963 value
= value
.parseColor();
965 } else if (property
== 'opacity') {
966 value
= parseFloat(value
);
967 if (Prototype
.Browser
.IE
&& (!this.element
.currentStyle
.hasLayout
))
968 this.element
.setStyle({zoom
: 1});
969 } else if (Element
.CSS_LENGTH
.test(value
)) {
970 var components
= value
.match(/^([\+\-]?[0-9\.]+)(.*)$/);
971 value
= parseFloat(components
[1]);
972 unit
= (components
.length
== 3) ? components
[2] : null;
975 var originalValue
= this.element
.getStyle(property
);
977 style
: property
.camelize(),
978 originalValue
: unit
=='color' ? parseColor(originalValue
) : parseFloat(originalValue
|| 0),
979 targetValue
: unit
=='color' ? parseColor(value
) : value
,
982 }.bind(this)).reject(function(transform
){
984 (transform
.originalValue
== transform
.targetValue
) ||
986 transform
.unit
!= 'color' &&
987 (isNaN(transform
.originalValue
) || isNaN(transform
.targetValue
))
992 update: function(position
) {
993 var style
= { }, transform
, i
= this.transforms
.length
;
995 style
[(transform
= this.transforms
[i
]).style
] =
996 transform
.unit
=='color' ? '#'+
997 (Math
.round(transform
.originalValue
[0]+
998 (transform
.targetValue
[0]-transform
.originalValue
[0])*position
)).toColorPart() +
999 (Math
.round(transform
.originalValue
[1]+
1000 (transform
.targetValue
[1]-transform
.originalValue
[1])*position
)).toColorPart() +
1001 (Math
.round(transform
.originalValue
[2]+
1002 (transform
.targetValue
[2]-transform
.originalValue
[2])*position
)).toColorPart() :
1003 (transform
.originalValue
+
1004 (transform
.targetValue
- transform
.originalValue
) * position
).toFixed(3) +
1005 (transform
.unit
=== null ? '' : transform
.unit
);
1006 this.element
.setStyle(style
, true);
1010 Effect
.Transform
= Class
.create({
1011 initialize: function(tracks
){
1013 this.options
= arguments
[1] || { };
1014 this.addTracks(tracks
);
1016 addTracks: function(tracks
){
1017 tracks
.each(function(track
){
1019 var data
= track
.values().first();
1020 this.tracks
.push($H({
1021 ids
: track
.keys().first(),
1022 effect
: Effect
.Morph
,
1023 options
: { style
: data
}
1029 return new Effect
.Parallel(
1030 this.tracks
.map(function(track
){
1031 var ids
= track
.get('ids'), effect
= track
.get('effect'), options
= track
.get('options');
1032 var elements
= [$(ids
) || $$(ids
)].flatten();
1033 return elements
.map(function(e
){ return new effect(e
, Object
.extend({ sync
:true }, options
)) });
1040 Element
.CSS_PROPERTIES
= $w(
1041 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' +
1042 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
1043 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
1044 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
1045 'fontSize fontWeight height left letterSpacing lineHeight ' +
1046 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
1047 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
1048 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
1049 'right textIndent top width wordSpacing zIndex');
1051 Element
.CSS_LENGTH
= /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
1053 String
.__parseStyleElement
= document
.createElement('div');
1054 String
.prototype.parseStyle = function(){
1055 var style
, styleRules
= $H();
1056 if (Prototype
.Browser
.WebKit
)
1057 style
= new Element('div',{style
:this}).style
;
1059 String
.__parseStyleElement
.innerHTML
= '<div style="' + this + '"></div>';
1060 style
= String
.__parseStyleElement
.childNodes
[0].style
;
1063 Element
.CSS_PROPERTIES
.each(function(property
){
1064 if (style
[property
]) styleRules
.set(property
, style
[property
]);
1067 if (Prototype
.Browser
.IE
&& this.include('opacity'))
1068 styleRules
.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]);
1073 if (document
.defaultView
&& document
.defaultView
.getComputedStyle
) {
1074 Element
.getStyles = function(element
) {
1075 var css
= document
.defaultView
.getComputedStyle($(element
), null);
1076 return Element
.CSS_PROPERTIES
.inject({ }, function(styles
, property
) {
1077 styles
[property
] = css
[property
];
1082 Element
.getStyles = function(element
) {
1083 element
= $(element
);
1084 var css
= element
.currentStyle
, styles
;
1085 styles
= Element
.CSS_PROPERTIES
.inject({ }, function(results
, property
) {
1086 results
[property
] = css
[property
];
1089 if (!styles
.opacity
) styles
.opacity
= element
.getOpacity();
1095 morph: function(element
, style
) {
1096 element
= $(element
);
1097 new Effect
.Morph(element
, Object
.extend({ style
: style
}, arguments
[2] || { }));
1100 visualEffect: function(element
, effect
, options
) {
1101 element
= $(element
);
1102 var s
= effect
.dasherize().camelize(), klass
= s
.charAt(0).toUpperCase() + s
.substring(1);
1103 new Effect
[klass
](element
, options
);
1106 highlight: function(element
, options
) {
1107 element
= $(element
);
1108 new Effect
.Highlight(element
, options
);
1113 $w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+
1114 'pulsate shake puff squish switchOff dropOut').each(
1116 Effect
.Methods
[effect
] = function(element
, options
){
1117 element
= $(element
);
1118 Effect
[effect
.charAt(0).toUpperCase() + effect
.substring(1)](element
, options
);
1124 $w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each(
1125 function(f
) { Effect
.Methods
[f
] = Element
[f
]; }
1128 Element
.addMethods(Effect
.Methods
);