1 // script.aculo.us effects.js v1.7.0, Fri Jan 19 19:16:36 CET 2007
3 // Copyright (c) 2005, 2006 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(navigator
.appVersion
.indexOf('AppleWebKit')>0) window
.scrollBy(0,0);
52 Element
.getOpacity = function(element
){
53 return $(element
).getStyle('opacity');
56 Element
.setOpacity = function(element
, value
){
57 return $(element
).setStyle({opacity
:value
});
60 Element
.getInlineOpacity = function(element
){
61 return $(element
).style
.opacity
|| '';
64 Element
.forceRerendering = function(element
) {
67 var n
= document
.createTextNode(' ');
68 element
.appendChild(n
);
69 element
.removeChild(n
);
73 /*--------------------------------------------------------------------------*/
75 Array
.prototype.call = function() {
77 this.each(function(f
){ f
.apply(this, args
) });
80 /*--------------------------------------------------------------------------*/
83 _elementDoesNotExistError
: {
84 name
: 'ElementDoesNotExistError',
85 message
: 'The specified DOM element does not exist, but is required for this effect to operate'
87 tagifyText: function(element
) {
88 if(typeof Builder
== 'undefined')
89 throw("Effect.tagifyText requires including script.aculo.us' builder.js library");
91 var tagifyStyle
= 'position:relative';
92 if(/MSIE/.test(navigator
.userAgent
) && !window
.opera
) tagifyStyle
+= ';zoom:1';
95 $A(element
.childNodes
).each( function(child
) {
96 if(child
.nodeType
==3) {
97 child
.nodeValue
.toArray().each( function(character
) {
99 Builder
.node('span',{style
: tagifyStyle
},
100 character
== ' ' ? String
.fromCharCode(160) : character
),
103 Element
.remove(child
);
107 multiple: function(element
, effect
) {
109 if(((typeof element
== 'object') ||
110 (typeof element
== 'function')) &&
114 elements
= $(element
).childNodes
;
116 var options
= Object
.extend({
119 }, arguments
[2] || {});
120 var masterDelay
= options
.delay
;
122 $A(elements
).each( function(element
, index
) {
123 new effect(element
, Object
.extend(options
, { delay
: index
* options
.speed
+ masterDelay
}));
127 'slide': ['SlideDown','SlideUp'],
128 'blind': ['BlindDown','BlindUp'],
129 'appear': ['Appear','Fade']
131 toggle: function(element
, effect
) {
132 element
= $(element
);
133 effect
= (effect
|| 'appear').toLowerCase();
134 var options
= Object
.extend({
135 queue
: { position
:'end', scope
:(element
.id
|| 'global'), limit
: 1 }
136 }, arguments
[2] || {});
137 Effect
[element
.visible() ?
138 Effect
.PAIRS
[effect
][1] : Effect
.PAIRS
[effect
][0]](element
, options
);
142 var Effect2
= Effect
; // deprecated
144 /* ------------- transitions ------------- */
146 Effect
.Transitions
= {
148 sinoidal: function(pos
) {
149 return (-Math
.cos(pos
*Math
.PI
)/2) + 0.5;
151 reverse: function(pos
) {
154 flicker: function(pos
) {
155 return ((-Math
.cos(pos
*Math
.PI
)/4) + 0.75) + Math.random()/4;
157 wobble: function(pos
) {
158 return (-Math
.cos(pos
*Math
.PI
*(9*pos
))/2) + 0.5;
160 pulse: function(pos
, pulses
) {
161 pulses
= pulses
|| 5;
163 Math
.round((pos
% (1/pulses
)) * pulses
) == 0 ?
164 ((pos
* pulses
* 2) - Math
.floor(pos
* pulses
* 2)) :
165 1 - ((pos
* pulses
* 2) - Math
.floor(pos
* pulses
* 2))
168 none: function(pos
) {
171 full: function(pos
) {
176 /* ------------- core effects ------------- */
178 Effect
.ScopedQueue
= Class
.create();
179 Object
.extend(Object
.extend(Effect
.ScopedQueue
.prototype, Enumerable
), {
180 initialize: function() {
182 this.interval
= null;
184 _each: function(iterator
) {
185 this.effects
._each(iterator
);
187 add: function(effect
) {
188 var timestamp
= new Date().getTime();
190 var position
= (typeof effect
.options
.queue
== 'string') ?
191 effect
.options
.queue
: effect
.options
.queue
.position
;
195 // move unstarted effects after this effect
196 this.effects
.findAll(function(e
){ return e
.state
=='idle' }).each( function(e
) {
197 e
.startOn
+= effect
.finishOn
;
198 e
.finishOn
+= effect
.finishOn
;
202 timestamp
= this.effects
.pluck('startOn').max() || timestamp
;
205 // start effect after last queued effect has finished
206 timestamp
= this.effects
.pluck('finishOn').max() || timestamp
;
210 effect
.startOn
+= timestamp
;
211 effect
.finishOn
+= timestamp
;
213 if(!effect
.options
.queue
.limit
|| (this.effects
.length
< effect
.options
.queue
.limit
))
214 this.effects
.push(effect
);
217 this.interval
= setInterval(this.loop
.bind(this), 15);
219 remove: function(effect
) {
220 this.effects
= this.effects
.reject(function(e
) { return e
==effect
});
221 if(this.effects
.length
== 0) {
222 clearInterval(this.interval
);
223 this.interval
= null;
227 var timePos
= new Date().getTime();
228 for(var i
=0, len
=this.effects
.length
;i
<len
;i
++)
229 if(this.effects
[i
]) this.effects
[i
].loop(timePos
);
235 get: function(queueName
) {
236 if(typeof queueName
!= 'string') return queueName
;
238 if(!this.instances
[queueName
])
239 this.instances
[queueName
] = new Effect
.ScopedQueue();
241 return this.instances
[queueName
];
244 Effect
.Queue
= Effect
.Queues
.get('global');
246 Effect
.DefaultOptions
= {
247 transition
: Effect
.Transitions
.sinoidal
,
248 duration
: 1.0, // seconds
249 fps
: 60.0, // max. 60fps due to Effect.Queue implementation
250 sync
: false, // true for combining
257 Effect
.Base = function() {};
258 Effect
.Base
.prototype = {
260 start: function(options
) {
261 this.options
= Object
.extend(Object
.extend({},Effect
.DefaultOptions
), options
|| {});
262 this.currentFrame
= 0;
264 this.startOn
= this.options
.delay
*1000;
265 this.finishOn
= this.startOn
+ (this.options
.duration
*1000);
266 this.event('beforeStart');
267 if(!this.options
.sync
)
268 Effect
.Queues
.get(typeof this.options
.queue
== 'string' ?
269 'global' : this.options
.queue
.scope
).add(this);
271 loop: function(timePos
) {
272 if(timePos
>= this.startOn
) {
273 if(timePos
>= this.finishOn
) {
276 this.event('beforeFinish');
277 if(this.finish
) this.finish();
278 this.event('afterFinish');
281 var pos
= (timePos
- this.startOn
) / (this.finishOn
- this.startOn
);
282 var frame
= Math
.round(pos
* this.options
.fps
* this.options
.duration
);
283 if(frame
> this.currentFrame
) {
285 this.currentFrame
= frame
;
289 render: function(pos
) {
290 if(this.state
== 'idle') {
291 this.state
= 'running';
292 this.event('beforeSetup');
293 if(this.setup
) this.setup();
294 this.event('afterSetup');
296 if(this.state
== 'running') {
297 if(this.options
.transition
) pos
= this.options
.transition(pos
);
298 pos
*= (this.options
.to
-this.options
.from);
299 pos
+= this.options
.from;
301 this.event('beforeUpdate');
302 if(this.update
) this.update(pos
);
303 this.event('afterUpdate');
307 if(!this.options
.sync
)
308 Effect
.Queues
.get(typeof this.options
.queue
== 'string' ?
309 'global' : this.options
.queue
.scope
).remove(this);
310 this.state
= 'finished';
312 event: function(eventName
) {
313 if(this.options
[eventName
+ 'Internal']) this.options
[eventName
+ 'Internal'](this);
314 if(this.options
[eventName
]) this.options
[eventName
](this);
316 inspect: function() {
318 for(property
in this)
319 if(typeof this[property
] != 'function') data
[property
] = this[property
];
320 return '#<Effect:' + data
.inspect() + ',options:' + $H(this.options
).inspect() + '>';
324 Effect
.Parallel
= Class
.create();
325 Object
.extend(Object
.extend(Effect
.Parallel
.prototype, Effect
.Base
.prototype), {
326 initialize: function(effects
) {
327 this.effects
= effects
|| [];
328 this.start(arguments
[1]);
330 update: function(position
) {
331 this.effects
.invoke('render', position
);
333 finish: function(position
) {
334 this.effects
.each( function(effect
) {
337 effect
.event('beforeFinish');
338 if(effect
.finish
) effect
.finish(position
);
339 effect
.event('afterFinish');
344 Effect
.Event
= Class
.create();
345 Object
.extend(Object
.extend(Effect
.Event
.prototype, Effect
.Base
.prototype), {
346 initialize: function() {
347 var options
= Object
.extend({
349 }, arguments
[0] || {});
352 update
: Prototype
.emptyFunction
355 Effect
.Opacity
= Class
.create();
356 Object
.extend(Object
.extend(Effect
.Opacity
.prototype, Effect
.Base
.prototype), {
357 initialize: function(element
) {
358 this.element
= $(element
);
359 if(!this.element
) throw(Effect
._elementDoesNotExistError
);
360 // make this work on IE on elements without 'layout'
361 if(/MSIE/.test(navigator
.userAgent
) && !window
.opera
&& (!this.element
.currentStyle
.hasLayout
))
362 this.element
.setStyle({zoom
: 1});
363 var options
= Object
.extend({
364 from: this.element
.getOpacity() || 0.0,
366 }, arguments
[1] || {});
369 update: function(position
) {
370 this.element
.setOpacity(position
);
374 Effect
.Move
= Class
.create();
375 Object
.extend(Object
.extend(Effect
.Move
.prototype, Effect
.Base
.prototype), {
376 initialize: function(element
) {
377 this.element
= $(element
);
378 if(!this.element
) throw(Effect
._elementDoesNotExistError
);
379 var options
= Object
.extend({
383 }, arguments
[1] || {});
387 // Bug in Opera: Opera returns the "real" position of a static element or
388 // relative element that does not have top/left explicitly set.
389 // ==> Always set top and left for position relative elements in your stylesheets
390 // (to 0 if you do not need them)
391 this.element
.makePositioned();
392 this.originalLeft
= parseFloat(this.element
.getStyle('left') || '0');
393 this.originalTop
= parseFloat(this.element
.getStyle('top') || '0');
394 if(this.options
.mode
== 'absolute') {
395 // absolute movement, so we need to calc deltaX and deltaY
396 this.options
.x
= this.options
.x
- this.originalLeft
;
397 this.options
.y
= this.options
.y
- this.originalTop
;
400 update: function(position
) {
401 this.element
.setStyle({
402 left
: Math
.round(this.options
.x
* position
+ this.originalLeft
) + 'px',
403 top
: Math
.round(this.options
.y
* position
+ this.originalTop
) + 'px'
408 // for backwards compatibility
409 Effect
.MoveBy = function(element
, toTop
, toLeft
) {
410 return new Effect
.Move(element
,
411 Object
.extend({ x
: toLeft
, y
: toTop
}, arguments
[3] || {}));
414 Effect
.Scale
= Class
.create();
415 Object
.extend(Object
.extend(Effect
.Scale
.prototype, Effect
.Base
.prototype), {
416 initialize: function(element
, percent
) {
417 this.element
= $(element
);
418 if(!this.element
) throw(Effect
._elementDoesNotExistError
);
419 var options
= Object
.extend({
423 scaleFromCenter
: false,
424 scaleMode
: 'box', // 'box' or 'contents' or {} with provided values
427 }, arguments
[2] || {});
431 this.restoreAfterFinish
= this.options
.restoreAfterFinish
|| false;
432 this.elementPositioning
= this.element
.getStyle('position');
434 this.originalStyle
= {};
435 ['top','left','width','height','fontSize'].each( function(k
) {
436 this.originalStyle
[k
] = this.element
.style
[k
];
439 this.originalTop
= this.element
.offsetTop
;
440 this.originalLeft
= this.element
.offsetLeft
;
442 var fontSize
= this.element
.getStyle('font-size') || '100%';
443 ['em','px','%','pt'].each( function(fontSizeType
) {
444 if(fontSize
.indexOf(fontSizeType
)>0) {
445 this.fontSize
= parseFloat(fontSize
);
446 this.fontSizeType
= fontSizeType
;
450 this.factor
= (this.options
.scaleTo
- this.options
.scaleFrom
)/100;
453 if(this.options
.scaleMode
=='box')
454 this.dims
= [this.element
.offsetHeight
, this.element
.offsetWidth
];
455 if(/^content/.test(this.options
.scaleMode
))
456 this.dims
= [this.element
.scrollHeight
, this.element
.scrollWidth
];
458 this.dims
= [this.options
.scaleMode
.originalHeight
,
459 this.options
.scaleMode
.originalWidth
];
461 update: function(position
) {
462 var currentScale
= (this.options
.scaleFrom
/100.0) + (this.factor
* position
);
463 if(this.options
.scaleContent
&& this.fontSize
)
464 this.element
.setStyle({fontSize
: this.fontSize
* currentScale
+ this.fontSizeType
});
465 this.setDimensions(this.dims
[0] * currentScale
, this.dims
[1] * currentScale
);
467 finish: function(position
) {
468 if(this.restoreAfterFinish
) this.element
.setStyle(this.originalStyle
);
470 setDimensions: function(height
, width
) {
472 if(this.options
.scaleX
) d
.width
= Math
.round(width
) + 'px';
473 if(this.options
.scaleY
) d
.height
= Math
.round(height
) + 'px';
474 if(this.options
.scaleFromCenter
) {
475 var topd
= (height
- this.dims
[0])/2;
476 var leftd
= (width
- this.dims
[1])/2;
477 if(this.elementPositioning
== 'absolute') {
478 if(this.options
.scaleY
) d
.top
= this.originalTop
-topd
+ 'px';
479 if(this.options
.scaleX
) d
.left
= this.originalLeft
-leftd
+ 'px';
481 if(this.options
.scaleY
) d
.top
= -topd
+ 'px';
482 if(this.options
.scaleX
) d
.left
= -leftd
+ 'px';
485 this.element
.setStyle(d
);
489 Effect
.Highlight
= Class
.create();
490 Object
.extend(Object
.extend(Effect
.Highlight
.prototype, Effect
.Base
.prototype), {
491 initialize: function(element
) {
492 this.element
= $(element
);
493 if(!this.element
) throw(Effect
._elementDoesNotExistError
);
494 var options
= Object
.extend({ startcolor
: '#ffff99' }, arguments
[1] || {});
498 // Prevent executing on elements not in the layout flow
499 if(this.element
.getStyle('display')=='none') { this.cancel(); return; }
500 // Disable background image during the effect
502 if (!this.options
.keepBackgroundImage
) {
503 this.oldStyle
.backgroundImage
= this.element
.getStyle('background-image');
504 this.element
.setStyle({backgroundImage
: 'none'});
506 if(!this.options
.endcolor
)
507 this.options
.endcolor
= this.element
.getStyle('background-color').parseColor('#ffffff');
508 if(!this.options
.restorecolor
)
509 this.options
.restorecolor
= this.element
.getStyle('background-color');
510 // init color calculations
511 this._base
= $R(0,2).map(function(i
){ return parseInt(this.options
.startcolor
.slice(i
*2+1,i
*2+3),16) }.bind(this));
512 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));
514 update: function(position
) {
515 this.element
.setStyle({backgroundColor
: $R(0,2).inject('#',function(m
,v
,i
){
516 return m
+(Math
.round(this._base
[i
]+(this._delta
[i
]*position
)).toColorPart()); }.bind(this)) });
519 this.element
.setStyle(Object
.extend(this.oldStyle
, {
520 backgroundColor
: this.options
.restorecolor
525 Effect
.ScrollTo
= Class
.create();
526 Object
.extend(Object
.extend(Effect
.ScrollTo
.prototype, Effect
.Base
.prototype), {
527 initialize: function(element
) {
528 this.element
= $(element
);
529 this.start(arguments
[1] || {});
533 var offsets
= Position
.cumulativeOffset(this.element
);
534 if(this.options
.offset
) offsets
[1] += this.options
.offset
;
535 var max
= window
.innerHeight
?
536 window
.height
- window
.innerHeight
:
537 document
.body
.scrollHeight
-
538 (document
.documentElement
.clientHeight
?
539 document
.documentElement
.clientHeight
: document
.body
.clientHeight
);
540 this.scrollStart
= Position
.deltaY
;
541 this.delta
= (offsets
[1] > max
? max
: offsets
[1]) - this.scrollStart
;
543 update: function(position
) {
545 window
.scrollTo(Position
.deltaX
,
546 this.scrollStart
+ (position
*this.delta
));
550 /* ------------- combination effects ------------- */
552 Effect
.Fade = function(element
) {
553 element
= $(element
);
554 var oldOpacity
= element
.getInlineOpacity();
555 var options
= Object
.extend({
556 from: element
.getOpacity() || 1.0,
558 afterFinishInternal: function(effect
) {
559 if(effect
.options
.to
!=0) return;
560 effect
.element
.hide().setStyle({opacity
: oldOpacity
});
561 }}, arguments
[1] || {});
562 return new Effect
.Opacity(element
,options
);
565 Effect
.Appear = function(element
) {
566 element
= $(element
);
567 var options
= Object
.extend({
568 from: (element
.getStyle('display') == 'none' ? 0.0 : element
.getOpacity() || 0.0),
570 // force Safari to render floated elements properly
571 afterFinishInternal: function(effect
) {
572 effect
.element
.forceRerendering();
574 beforeSetup: function(effect
) {
575 effect
.element
.setOpacity(effect
.options
.from).show();
576 }}, arguments
[1] || {});
577 return new Effect
.Opacity(element
,options
);
580 Effect
.Puff = function(element
) {
581 element
= $(element
);
583 opacity
: element
.getInlineOpacity(),
584 position
: element
.getStyle('position'),
585 top
: element
.style
.top
,
586 left
: element
.style
.left
,
587 width
: element
.style
.width
,
588 height
: element
.style
.height
590 return new Effect
.Parallel(
591 [ new Effect
.Scale(element
, 200,
592 { sync
: true, scaleFromCenter
: true, scaleContent
: true, restoreAfterFinish
: true }),
593 new Effect
.Opacity(element
, { sync
: true, to
: 0.0 } ) ],
594 Object
.extend({ duration
: 1.0,
595 beforeSetupInternal: function(effect
) {
596 Position
.absolutize(effect
.effects
[0].element
)
598 afterFinishInternal: function(effect
) {
599 effect
.effects
[0].element
.hide().setStyle(oldStyle
); }
600 }, arguments
[1] || {})
604 Effect
.BlindUp = function(element
) {
605 element
= $(element
);
606 element
.makeClipping();
607 return new Effect
.Scale(element
, 0,
608 Object
.extend({ scaleContent
: false,
610 restoreAfterFinish
: true,
611 afterFinishInternal: function(effect
) {
612 effect
.element
.hide().undoClipping();
614 }, arguments
[1] || {})
618 Effect
.BlindDown = function(element
) {
619 element
= $(element
);
620 var elementDimensions
= element
.getDimensions();
621 return new Effect
.Scale(element
, 100, Object
.extend({
625 scaleMode
: {originalHeight
: elementDimensions
.height
, originalWidth
: elementDimensions
.width
},
626 restoreAfterFinish
: true,
627 afterSetup: function(effect
) {
628 effect
.element
.makeClipping().setStyle({height
: '0px'}).show();
630 afterFinishInternal: function(effect
) {
631 effect
.element
.undoClipping();
633 }, arguments
[1] || {}));
636 Effect
.SwitchOff = function(element
) {
637 element
= $(element
);
638 var oldOpacity
= element
.getInlineOpacity();
639 return new Effect
.Appear(element
, Object
.extend({
642 transition
: Effect
.Transitions
.flicker
,
643 afterFinishInternal: function(effect
) {
644 new Effect
.Scale(effect
.element
, 1, {
645 duration
: 0.3, scaleFromCenter
: true,
646 scaleX
: false, scaleContent
: false, restoreAfterFinish
: true,
647 beforeSetup: function(effect
) {
648 effect
.element
.makePositioned().makeClipping();
650 afterFinishInternal: function(effect
) {
651 effect
.element
.hide().undoClipping().undoPositioned().setStyle({opacity
: oldOpacity
});
655 }, arguments
[1] || {}));
658 Effect
.DropOut = function(element
) {
659 element
= $(element
);
661 top
: element
.getStyle('top'),
662 left
: element
.getStyle('left'),
663 opacity
: element
.getInlineOpacity() };
664 return new Effect
.Parallel(
665 [ new Effect
.Move(element
, {x
: 0, y
: 100, sync
: true }),
666 new Effect
.Opacity(element
, { sync
: true, to
: 0.0 }) ],
669 beforeSetup: function(effect
) {
670 effect
.effects
[0].element
.makePositioned();
672 afterFinishInternal: function(effect
) {
673 effect
.effects
[0].element
.hide().undoPositioned().setStyle(oldStyle
);
675 }, arguments
[1] || {}));
678 Effect
.Shake = function(element
) {
679 element
= $(element
);
681 top
: element
.getStyle('top'),
682 left
: element
.getStyle('left') };
683 return new Effect
.Move(element
,
684 { x
: 20, y
: 0, duration
: 0.05, afterFinishInternal: function(effect
) {
685 new Effect
.Move(effect
.element
,
686 { x
: -40, y
: 0, duration
: 0.1, afterFinishInternal: function(effect
) {
687 new Effect
.Move(effect
.element
,
688 { x
: 40, y
: 0, duration
: 0.1, afterFinishInternal: function(effect
) {
689 new Effect
.Move(effect
.element
,
690 { x
: -40, y
: 0, duration
: 0.1, afterFinishInternal: function(effect
) {
691 new Effect
.Move(effect
.element
,
692 { x
: 40, y
: 0, duration
: 0.1, afterFinishInternal: function(effect
) {
693 new Effect
.Move(effect
.element
,
694 { x
: -20, y
: 0, duration
: 0.05, afterFinishInternal: function(effect
) {
695 effect
.element
.undoPositioned().setStyle(oldStyle
);
696 }}) }}) }}) }}) }}) }});
699 Effect
.SlideDown = function(element
) {
700 element
= $(element
).cleanWhitespace();
701 // SlideDown need to have the content of the element wrapped in a container element with fixed height!
702 var oldInnerBottom
= element
.down().getStyle('bottom');
703 var elementDimensions
= element
.getDimensions();
704 return new Effect
.Scale(element
, 100, Object
.extend({
707 scaleFrom
: window
.opera
? 0 : 1,
708 scaleMode
: {originalHeight
: elementDimensions
.height
, originalWidth
: elementDimensions
.width
},
709 restoreAfterFinish
: true,
710 afterSetup: function(effect
) {
711 effect
.element
.makePositioned();
712 effect
.element
.down().makePositioned();
713 if(window
.opera
) effect
.element
.setStyle({top
: ''});
714 effect
.element
.makeClipping().setStyle({height
: '0px'}).show();
716 afterUpdateInternal: function(effect
) {
717 effect
.element
.down().setStyle({bottom
:
718 (effect
.dims
[0] - effect
.element
.clientHeight
) + 'px' });
720 afterFinishInternal: function(effect
) {
721 effect
.element
.undoClipping().undoPositioned();
722 effect
.element
.down().undoPositioned().setStyle({bottom
: oldInnerBottom
}); }
723 }, arguments
[1] || {})
727 Effect
.SlideUp = function(element
) {
728 element
= $(element
).cleanWhitespace();
729 var oldInnerBottom
= element
.down().getStyle('bottom');
730 return new Effect
.Scale(element
, window
.opera
? 0 : 1,
731 Object
.extend({ scaleContent
: false,
735 restoreAfterFinish
: true,
736 beforeStartInternal: function(effect
) {
737 effect
.element
.makePositioned();
738 effect
.element
.down().makePositioned();
739 if(window
.opera
) effect
.element
.setStyle({top
: ''});
740 effect
.element
.makeClipping().show();
742 afterUpdateInternal: function(effect
) {
743 effect
.element
.down().setStyle({bottom
:
744 (effect
.dims
[0] - effect
.element
.clientHeight
) + 'px' });
746 afterFinishInternal: function(effect
) {
747 effect
.element
.hide().undoClipping().undoPositioned().setStyle({bottom
: oldInnerBottom
});
748 effect
.element
.down().undoPositioned();
750 }, arguments
[1] || {})
754 // Bug in opera makes the TD containing this element expand for a instance after finish
755 Effect
.Squish = function(element
) {
756 return new Effect
.Scale(element
, window
.opera
? 1 : 0, {
757 restoreAfterFinish
: true,
758 beforeSetup: function(effect
) {
759 effect
.element
.makeClipping();
761 afterFinishInternal: function(effect
) {
762 effect
.element
.hide().undoClipping();
767 Effect
.Grow = function(element
) {
768 element
= $(element
);
769 var options
= Object
.extend({
771 moveTransition
: Effect
.Transitions
.sinoidal
,
772 scaleTransition
: Effect
.Transitions
.sinoidal
,
773 opacityTransition
: Effect
.Transitions
.full
774 }, arguments
[1] || {});
776 top
: element
.style
.top
,
777 left
: element
.style
.left
,
778 height
: element
.style
.height
,
779 width
: element
.style
.width
,
780 opacity
: element
.getInlineOpacity() };
782 var dims
= element
.getDimensions();
783 var initialMoveX
, initialMoveY
;
786 switch (options
.direction
) {
788 initialMoveX
= initialMoveY
= moveX
= moveY
= 0;
791 initialMoveX
= dims
.width
;
792 initialMoveY
= moveY
= 0;
796 initialMoveX
= moveX
= 0;
797 initialMoveY
= dims
.height
;
798 moveY
= -dims
.height
;
801 initialMoveX
= dims
.width
;
802 initialMoveY
= dims
.height
;
804 moveY
= -dims
.height
;
807 initialMoveX
= dims
.width
/ 2;
808 initialMoveY
= dims
.height
/ 2;
809 moveX
= -dims
.width
/ 2;
810 moveY
= -dims
.height
/ 2;
814 return new Effect
.Move(element
, {
818 beforeSetup: function(effect
) {
819 effect
.element
.hide().makeClipping().makePositioned();
821 afterFinishInternal: function(effect
) {
823 [ new Effect
.Opacity(effect
.element
, { sync
: true, to
: 1.0, from: 0.0, transition
: options
.opacityTransition
}),
824 new Effect
.Move(effect
.element
, { x
: moveX
, y
: moveY
, sync
: true, transition
: options
.moveTransition
}),
825 new Effect
.Scale(effect
.element
, 100, {
826 scaleMode
: { originalHeight
: dims
.height
, originalWidth
: dims
.width
},
827 sync
: true, scaleFrom
: window
.opera
? 1 : 0, transition
: options
.scaleTransition
, restoreAfterFinish
: true})
829 beforeSetup: function(effect
) {
830 effect
.effects
[0].element
.setStyle({height
: '0px'}).show();
832 afterFinishInternal: function(effect
) {
833 effect
.effects
[0].element
.undoClipping().undoPositioned().setStyle(oldStyle
);
841 Effect
.Shrink = function(element
) {
842 element
= $(element
);
843 var options
= Object
.extend({
845 moveTransition
: Effect
.Transitions
.sinoidal
,
846 scaleTransition
: Effect
.Transitions
.sinoidal
,
847 opacityTransition
: Effect
.Transitions
.none
848 }, arguments
[1] || {});
850 top
: element
.style
.top
,
851 left
: element
.style
.left
,
852 height
: element
.style
.height
,
853 width
: element
.style
.width
,
854 opacity
: element
.getInlineOpacity() };
856 var dims
= element
.getDimensions();
859 switch (options
.direction
) {
876 moveX
= dims
.width
/ 2;
877 moveY
= dims
.height
/ 2;
881 return new Effect
.Parallel(
882 [ new Effect
.Opacity(element
, { sync
: true, to
: 0.0, from: 1.0, transition
: options
.opacityTransition
}),
883 new Effect
.Scale(element
, window
.opera
? 1 : 0, { sync
: true, transition
: options
.scaleTransition
, restoreAfterFinish
: true}),
884 new Effect
.Move(element
, { x
: moveX
, y
: moveY
, sync
: true, transition
: options
.moveTransition
})
886 beforeStartInternal: function(effect
) {
887 effect
.effects
[0].element
.makePositioned().makeClipping();
889 afterFinishInternal: function(effect
) {
890 effect
.effects
[0].element
.hide().undoClipping().undoPositioned().setStyle(oldStyle
); }
895 Effect
.Pulsate = function(element
) {
896 element
= $(element
);
897 var options
= arguments
[1] || {};
898 var oldOpacity
= element
.getInlineOpacity();
899 var transition
= options
.transition
|| Effect
.Transitions
.sinoidal
;
900 var reverser = function(pos
){ return transition(1-Effect
.Transitions
.pulse(pos
, options
.pulses
)) };
901 reverser
.bind(transition
);
902 return new Effect
.Opacity(element
,
903 Object
.extend(Object
.extend({ duration
: 2.0, from: 0,
904 afterFinishInternal: function(effect
) { effect
.element
.setStyle({opacity
: oldOpacity
}); }
905 }, options
), {transition
: reverser
}));
908 Effect
.Fold = function(element
) {
909 element
= $(element
);
911 top
: element
.style
.top
,
912 left
: element
.style
.left
,
913 width
: element
.style
.width
,
914 height
: element
.style
.height
};
915 element
.makeClipping();
916 return new Effect
.Scale(element
, 5, Object
.extend({
919 afterFinishInternal: function(effect
) {
920 new Effect
.Scale(element
, 1, {
923 afterFinishInternal: function(effect
) {
924 effect
.element
.hide().undoClipping().setStyle(oldStyle
);
926 }}, arguments
[1] || {}));
929 Effect
.Morph
= Class
.create();
930 Object
.extend(Object
.extend(Effect
.Morph
.prototype, Effect
.Base
.prototype), {
931 initialize: function(element
) {
932 this.element
= $(element
);
933 if(!this.element
) throw(Effect
._elementDoesNotExistError
);
934 var options
= Object
.extend({
936 }, arguments
[1] || {});
937 if (typeof options
.style
== 'string') {
938 if(options
.style
.indexOf(':') == -1) {
939 var cssText
= '', selector
= '.' + options
.style
;
940 $A(document
.styleSheets
).reverse().each(function(styleSheet
) {
941 if (styleSheet
.cssRules
) cssRules
= styleSheet
.cssRules
;
942 else if (styleSheet
.rules
) cssRules
= styleSheet
.rules
;
943 $A(cssRules
).reverse().each(function(rule
) {
944 if (selector
== rule
.selectorText
) {
945 cssText
= rule
.style
.cssText
;
949 if (cssText
) throw $break;
951 this.style
= cssText
.parseStyle();
952 options
.afterFinishInternal = function(effect
){
953 effect
.element
.addClassName(effect
.options
.style
);
954 effect
.transforms
.each(function(transform
) {
955 if(transform
.style
!= 'opacity')
956 effect
.element
.style
[transform
.style
.camelize()] = '';
959 } else this.style
= options
.style
.parseStyle();
960 } else this.style
= $H(options
.style
)
964 function parseColor(color
){
965 if(!color
|| ['rgba(0, 0, 0, 0)','transparent'].include(color
)) color
= '#ffffff';
966 color
= color
.parseColor();
967 return $R(0,2).map(function(i
){
968 return parseInt( color
.slice(i
*2+1,i
*2+3), 16 )
971 this.transforms
= this.style
.map(function(pair
){
972 var property
= pair
[0].underscore().dasherize(), value
= pair
[1], unit
= null;
974 if(value
.parseColor('#zzzzzz') != '#zzzzzz') {
975 value
= value
.parseColor();
977 } else if(property
== 'opacity') {
978 value
= parseFloat(value
);
979 if(/MSIE/.test(navigator
.userAgent
) && !window
.opera
&& (!this.element
.currentStyle
.hasLayout
))
980 this.element
.setStyle({zoom
: 1});
981 } else if(Element
.CSS_LENGTH
.test(value
))
982 var components
= value
.match(/^([\+\-]?[0-9\.]+)(.*)$/),
983 value
= parseFloat(components
[1]), unit
= (components
.length
== 3) ? components
[2] : null;
985 var originalValue
= this.element
.getStyle(property
);
988 originalValue
: unit
=='color' ? parseColor(originalValue
) : parseFloat(originalValue
|| 0),
989 targetValue
: unit
=='color' ? parseColor(value
) : value
,
992 }.bind(this)).reject(function(transform
){
994 (transform
.originalValue
== transform
.targetValue
) ||
996 transform
.unit
!= 'color' &&
997 (isNaN(transform
.originalValue
) || isNaN(transform
.targetValue
))
1002 update: function(position
) {
1003 var style
= $H(), value
= null;
1004 this.transforms
.each(function(transform
){
1005 value
= transform
.unit
=='color' ?
1006 $R(0,2).inject('#',function(m
,v
,i
){
1007 return m
+(Math
.round(transform
.originalValue
[i
]+
1008 (transform
.targetValue
[i
] - transform
.originalValue
[i
])*position
)).toColorPart() }) :
1009 transform
.originalValue
+ Math
.round(
1010 ((transform
.targetValue
- transform
.originalValue
) * position
) * 1000)/1000 + transform
.unit
;
1011 style
[transform
.style
] = value
;
1013 this.element
.setStyle(style
);
1017 Effect
.Transform
= Class
.create();
1018 Object
.extend(Effect
.Transform
.prototype, {
1019 initialize: function(tracks
){
1021 this.options
= arguments
[1] || {};
1022 this.addTracks(tracks
);
1024 addTracks: function(tracks
){
1025 tracks
.each(function(track
){
1026 var data
= $H(track
).values().first();
1027 this.tracks
.push($H({
1028 ids
: $H(track
).keys().first(),
1029 effect
: Effect
.Morph
,
1030 options
: { style
: data
}
1036 return new Effect
.Parallel(
1037 this.tracks
.map(function(track
){
1038 var elements
= [$(track
.ids
) || $$(track
.ids
)].flatten();
1039 return elements
.map(function(e
){ return new track
.effect(e
, Object
.extend({ sync
:true }, track
.options
)) });
1046 Element
.CSS_PROPERTIES
= $w(
1047 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' +
1048 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
1049 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
1050 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
1051 'fontSize fontWeight height left letterSpacing lineHeight ' +
1052 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
1053 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
1054 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
1055 'right textIndent top width wordSpacing zIndex');
1057 Element
.CSS_LENGTH
= /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
1059 String
.prototype.parseStyle = function(){
1060 var element
= Element
.extend(document
.createElement('div'));
1061 element
.innerHTML
= '<div style="' + this + '"></div>';
1062 var style
= element
.down().style
, styleRules
= $H();
1064 Element
.CSS_PROPERTIES
.each(function(property
){
1065 if(style
[property
]) styleRules
[property
] = style
[property
];
1067 if(/MSIE/.test(navigator
.userAgent
) && !window
.opera
&& this.indexOf('opacity') > -1) {
1068 styleRules
.opacity
= this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1];
1073 Element
.morph = function(element
, style
) {
1074 new Effect
.Morph(element
, Object
.extend({ style
: style
}, arguments
[2] || {}));
1078 ['setOpacity','getOpacity','getInlineOpacity','forceRerendering','setContentZoom',
1079 'collectTextNodes','collectTextNodesIgnoreClass','morph'].each(
1080 function(f
) { Element
.Methods
[f
] = Element
[f
]; }
1083 Element
.Methods
.visualEffect = function(element
, effect
, options
) {
1084 s
= effect
.gsub(/_
/, '-').camelize();
1085 effect_class
= s
.charAt(0).toUpperCase() + s
.substring(1);
1086 new Effect
[effect_class
](element
, options
);
1090 Element
.addMethods();