initial import boxroom 0.6.2
[boxroom-stian.git] / public / javascripts / effects.js
blob7ca77315445205b6a4b25b1a9253d83b72a79888
1 // Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2 // Contributors:
3 // Justin Palmer (http://encytemedia.com/)
4 // Mark Pilgrim (http://diveintomark.org/)
5 // Martin Bialasinki
6 //
7 // 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() {
13 var color = '#';
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);
17 } else {
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) {
44 element = $(element);
45 element.setStyle({fontSize: (percent/100) + 'em'});
46 if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
47 return element;
50 Element.getOpacity = function(element){
51 element = $(element);
52 var opacity;
53 if (opacity = element.getStyle('opacity'))
54 return parseFloat(opacity);
55 if (opacity = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
56 if(opacity[1]) return parseFloat(opacity[1]) / 100;
57 return 1.0;
60 Element.setOpacity = function(element, value){
61 element= $(element);
62 if (value == 1){
63 element.setStyle({ opacity:
64 (/Gecko/.test(navigator.userAgent) && !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ?
65 0.999999 : 1.0 });
66 if(/MSIE/.test(navigator.userAgent) && !window.opera)
67 element.setStyle({filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')});
68 } else {
69 if(value < 0.00001) value = 0;
70 element.setStyle({opacity: value});
71 if(/MSIE/.test(navigator.userAgent) && !window.opera)
72 element.setStyle(
73 { filter: element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'') +
74 'alpha(opacity='+value*100+')' });
76 return element;
79 Element.getInlineOpacity = function(element){
80 return $(element).style.opacity || '';
83 Element.forceRerendering = function(element) {
84 try {
85 element = $(element);
86 var n = document.createTextNode(' ');
87 element.appendChild(n);
88 element.removeChild(n);
89 } catch(e) { }
92 /*--------------------------------------------------------------------------*/
94 Array.prototype.call = function() {
95 var args = arguments;
96 this.each(function(f){ f.apply(this, args) });
99 /*--------------------------------------------------------------------------*/
101 var Effect = {
102 _elementDoesNotExistError: {
103 name: 'ElementDoesNotExistError',
104 message: 'The specified DOM element does not exist, but is required for this effect to operate'
106 tagifyText: function(element) {
107 if(typeof Builder == 'undefined')
108 throw("Effect.tagifyText requires including script.aculo.us' builder.js library");
110 var tagifyStyle = 'position:relative';
111 if(/MSIE/.test(navigator.userAgent) && !window.opera) tagifyStyle += ';zoom:1';
113 element = $(element);
114 $A(element.childNodes).each( function(child) {
115 if(child.nodeType==3) {
116 child.nodeValue.toArray().each( function(character) {
117 element.insertBefore(
118 Builder.node('span',{style: tagifyStyle},
119 character == ' ' ? String.fromCharCode(160) : character),
120 child);
122 Element.remove(child);
126 multiple: function(element, effect) {
127 var elements;
128 if(((typeof element == 'object') ||
129 (typeof element == 'function')) &&
130 (element.length))
131 elements = element;
132 else
133 elements = $(element).childNodes;
135 var options = Object.extend({
136 speed: 0.1,
137 delay: 0.0
138 }, arguments[2] || {});
139 var masterDelay = options.delay;
141 $A(elements).each( function(element, index) {
142 new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
145 PAIRS: {
146 'slide': ['SlideDown','SlideUp'],
147 'blind': ['BlindDown','BlindUp'],
148 'appear': ['Appear','Fade']
150 toggle: function(element, effect) {
151 element = $(element);
152 effect = (effect || 'appear').toLowerCase();
153 var options = Object.extend({
154 queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
155 }, arguments[2] || {});
156 Effect[element.visible() ?
157 Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
161 var Effect2 = Effect; // deprecated
163 /* ------------- transitions ------------- */
165 Effect.Transitions = {
166 linear: Prototype.K,
167 sinoidal: function(pos) {
168 return (-Math.cos(pos*Math.PI)/2) + 0.5;
170 reverse: function(pos) {
171 return 1-pos;
173 flicker: function(pos) {
174 return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
176 wobble: function(pos) {
177 return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
179 pulse: function(pos, pulses) {
180 pulses = pulses || 5;
181 return (
182 Math.round((pos % (1/pulses)) * pulses) == 0 ?
183 ((pos * pulses * 2) - Math.floor(pos * pulses * 2)) :
184 1 - ((pos * pulses * 2) - Math.floor(pos * pulses * 2))
187 none: function(pos) {
188 return 0;
190 full: function(pos) {
191 return 1;
195 /* ------------- core effects ------------- */
197 Effect.ScopedQueue = Class.create();
198 Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), {
199 initialize: function() {
200 this.effects = [];
201 this.interval = null;
203 _each: function(iterator) {
204 this.effects._each(iterator);
206 add: function(effect) {
207 var timestamp = new Date().getTime();
209 var position = (typeof effect.options.queue == 'string') ?
210 effect.options.queue : effect.options.queue.position;
212 switch(position) {
213 case 'front':
214 // move unstarted effects after this effect
215 this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
216 e.startOn += effect.finishOn;
217 e.finishOn += effect.finishOn;
219 break;
220 case 'with-last':
221 timestamp = this.effects.pluck('startOn').max() || timestamp;
222 break;
223 case 'end':
224 // start effect after last queued effect has finished
225 timestamp = this.effects.pluck('finishOn').max() || timestamp;
226 break;
229 effect.startOn += timestamp;
230 effect.finishOn += timestamp;
232 if(!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
233 this.effects.push(effect);
235 if(!this.interval)
236 this.interval = setInterval(this.loop.bind(this), 40);
238 remove: function(effect) {
239 this.effects = this.effects.reject(function(e) { return e==effect });
240 if(this.effects.length == 0) {
241 clearInterval(this.interval);
242 this.interval = null;
245 loop: function() {
246 var timePos = new Date().getTime();
247 this.effects.invoke('loop', timePos);
251 Effect.Queues = {
252 instances: $H(),
253 get: function(queueName) {
254 if(typeof queueName != 'string') return queueName;
256 if(!this.instances[queueName])
257 this.instances[queueName] = new Effect.ScopedQueue();
259 return this.instances[queueName];
262 Effect.Queue = Effect.Queues.get('global');
264 Effect.DefaultOptions = {
265 transition: Effect.Transitions.sinoidal,
266 duration: 1.0, // seconds
267 fps: 25.0, // max. 25fps due to Effect.Queue implementation
268 sync: false, // true for combining
269 from: 0.0,
270 to: 1.0,
271 delay: 0.0,
272 queue: 'parallel'
275 Effect.Base = function() {};
276 Effect.Base.prototype = {
277 position: null,
278 start: function(options) {
279 this.options = Object.extend(Object.extend({},Effect.DefaultOptions), options || {});
280 this.currentFrame = 0;
281 this.state = 'idle';
282 this.startOn = this.options.delay*1000;
283 this.finishOn = this.startOn + (this.options.duration*1000);
284 this.event('beforeStart');
285 if(!this.options.sync)
286 Effect.Queues.get(typeof this.options.queue == 'string' ?
287 'global' : this.options.queue.scope).add(this);
289 loop: function(timePos) {
290 if(timePos >= this.startOn) {
291 if(timePos >= this.finishOn) {
292 this.render(1.0);
293 this.cancel();
294 this.event('beforeFinish');
295 if(this.finish) this.finish();
296 this.event('afterFinish');
297 return;
299 var pos = (timePos - this.startOn) / (this.finishOn - this.startOn);
300 var frame = Math.round(pos * this.options.fps * this.options.duration);
301 if(frame > this.currentFrame) {
302 this.render(pos);
303 this.currentFrame = frame;
307 render: function(pos) {
308 if(this.state == 'idle') {
309 this.state = 'running';
310 this.event('beforeSetup');
311 if(this.setup) this.setup();
312 this.event('afterSetup');
314 if(this.state == 'running') {
315 if(this.options.transition) pos = this.options.transition(pos);
316 pos *= (this.options.to-this.options.from);
317 pos += this.options.from;
318 this.position = pos;
319 this.event('beforeUpdate');
320 if(this.update) this.update(pos);
321 this.event('afterUpdate');
324 cancel: function() {
325 if(!this.options.sync)
326 Effect.Queues.get(typeof this.options.queue == 'string' ?
327 'global' : this.options.queue.scope).remove(this);
328 this.state = 'finished';
330 event: function(eventName) {
331 if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
332 if(this.options[eventName]) this.options[eventName](this);
334 inspect: function() {
335 return '#<Effect:' + $H(this).inspect() + ',options:' + $H(this.options).inspect() + '>';
339 Effect.Parallel = Class.create();
340 Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), {
341 initialize: function(effects) {
342 this.effects = effects || [];
343 this.start(arguments[1]);
345 update: function(position) {
346 this.effects.invoke('render', position);
348 finish: function(position) {
349 this.effects.each( function(effect) {
350 effect.render(1.0);
351 effect.cancel();
352 effect.event('beforeFinish');
353 if(effect.finish) effect.finish(position);
354 effect.event('afterFinish');
359 Effect.Event = Class.create();
360 Object.extend(Object.extend(Effect.Event.prototype, Effect.Base.prototype), {
361 initialize: function() {
362 var options = Object.extend({
363 duration: 0
364 }, arguments[0] || {});
365 this.start(options);
367 update: Prototype.emptyFunction
370 Effect.Opacity = Class.create();
371 Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), {
372 initialize: function(element) {
373 this.element = $(element);
374 if(!this.element) throw(Effect._elementDoesNotExistError);
375 // make this work on IE on elements without 'layout'
376 if(/MSIE/.test(navigator.userAgent) && !window.opera && (!this.element.currentStyle.hasLayout))
377 this.element.setStyle({zoom: 1});
378 var options = Object.extend({
379 from: this.element.getOpacity() || 0.0,
380 to: 1.0
381 }, arguments[1] || {});
382 this.start(options);
384 update: function(position) {
385 this.element.setOpacity(position);
389 Effect.Move = Class.create();
390 Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), {
391 initialize: function(element) {
392 this.element = $(element);
393 if(!this.element) throw(Effect._elementDoesNotExistError);
394 var options = Object.extend({
395 x: 0,
396 y: 0,
397 mode: 'relative'
398 }, arguments[1] || {});
399 this.start(options);
401 setup: function() {
402 // Bug in Opera: Opera returns the "real" position of a static element or
403 // relative element that does not have top/left explicitly set.
404 // ==> Always set top and left for position relative elements in your stylesheets
405 // (to 0 if you do not need them)
406 this.element.makePositioned();
407 this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
408 this.originalTop = parseFloat(this.element.getStyle('top') || '0');
409 if(this.options.mode == 'absolute') {
410 // absolute movement, so we need to calc deltaX and deltaY
411 this.options.x = this.options.x - this.originalLeft;
412 this.options.y = this.options.y - this.originalTop;
415 update: function(position) {
416 this.element.setStyle({
417 left: Math.round(this.options.x * position + this.originalLeft) + 'px',
418 top: Math.round(this.options.y * position + this.originalTop) + 'px'
423 // for backwards compatibility
424 Effect.MoveBy = function(element, toTop, toLeft) {
425 return new Effect.Move(element,
426 Object.extend({ x: toLeft, y: toTop }, arguments[3] || {}));
429 Effect.Scale = Class.create();
430 Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
431 initialize: function(element, percent) {
432 this.element = $(element);
433 if(!this.element) throw(Effect._elementDoesNotExistError);
434 var options = Object.extend({
435 scaleX: true,
436 scaleY: true,
437 scaleContent: true,
438 scaleFromCenter: false,
439 scaleMode: 'box', // 'box' or 'contents' or {} with provided values
440 scaleFrom: 100.0,
441 scaleTo: percent
442 }, arguments[2] || {});
443 this.start(options);
445 setup: function() {
446 this.restoreAfterFinish = this.options.restoreAfterFinish || false;
447 this.elementPositioning = this.element.getStyle('position');
449 this.originalStyle = {};
450 ['top','left','width','height','fontSize'].each( function(k) {
451 this.originalStyle[k] = this.element.style[k];
452 }.bind(this));
454 this.originalTop = this.element.offsetTop;
455 this.originalLeft = this.element.offsetLeft;
457 var fontSize = this.element.getStyle('font-size') || '100%';
458 ['em','px','%','pt'].each( function(fontSizeType) {
459 if(fontSize.indexOf(fontSizeType)>0) {
460 this.fontSize = parseFloat(fontSize);
461 this.fontSizeType = fontSizeType;
463 }.bind(this));
465 this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
467 this.dims = null;
468 if(this.options.scaleMode=='box')
469 this.dims = [this.element.offsetHeight, this.element.offsetWidth];
470 if(/^content/.test(this.options.scaleMode))
471 this.dims = [this.element.scrollHeight, this.element.scrollWidth];
472 if(!this.dims)
473 this.dims = [this.options.scaleMode.originalHeight,
474 this.options.scaleMode.originalWidth];
476 update: function(position) {
477 var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
478 if(this.options.scaleContent && this.fontSize)
479 this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
480 this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
482 finish: function(position) {
483 if(this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
485 setDimensions: function(height, width) {
486 var d = {};
487 if(this.options.scaleX) d.width = Math.round(width) + 'px';
488 if(this.options.scaleY) d.height = Math.round(height) + 'px';
489 if(this.options.scaleFromCenter) {
490 var topd = (height - this.dims[0])/2;
491 var leftd = (width - this.dims[1])/2;
492 if(this.elementPositioning == 'absolute') {
493 if(this.options.scaleY) d.top = this.originalTop-topd + 'px';
494 if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
495 } else {
496 if(this.options.scaleY) d.top = -topd + 'px';
497 if(this.options.scaleX) d.left = -leftd + 'px';
500 this.element.setStyle(d);
504 Effect.Highlight = Class.create();
505 Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), {
506 initialize: function(element) {
507 this.element = $(element);
508 if(!this.element) throw(Effect._elementDoesNotExistError);
509 var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {});
510 this.start(options);
512 setup: function() {
513 // Prevent executing on elements not in the layout flow
514 if(this.element.getStyle('display')=='none') { this.cancel(); return; }
515 // Disable background image during the effect
516 this.oldStyle = {
517 backgroundImage: this.element.getStyle('background-image') };
518 this.element.setStyle({backgroundImage: 'none'});
519 if(!this.options.endcolor)
520 this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
521 if(!this.options.restorecolor)
522 this.options.restorecolor = this.element.getStyle('background-color');
523 // init color calculations
524 this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
525 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));
527 update: function(position) {
528 this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
529 return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) });
531 finish: function() {
532 this.element.setStyle(Object.extend(this.oldStyle, {
533 backgroundColor: this.options.restorecolor
534 }));
538 Effect.ScrollTo = Class.create();
539 Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
540 initialize: function(element) {
541 this.element = $(element);
542 this.start(arguments[1] || {});
544 setup: function() {
545 Position.prepare();
546 var offsets = Position.cumulativeOffset(this.element);
547 if(this.options.offset) offsets[1] += this.options.offset;
548 var max = window.innerHeight ?
549 window.height - window.innerHeight :
550 document.body.scrollHeight -
551 (document.documentElement.clientHeight ?
552 document.documentElement.clientHeight : document.body.clientHeight);
553 this.scrollStart = Position.deltaY;
554 this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart;
556 update: function(position) {
557 Position.prepare();
558 window.scrollTo(Position.deltaX,
559 this.scrollStart + (position*this.delta));
563 /* ------------- combination effects ------------- */
565 Effect.Fade = function(element) {
566 element = $(element);
567 var oldOpacity = element.getInlineOpacity();
568 var options = Object.extend({
569 from: element.getOpacity() || 1.0,
570 to: 0.0,
571 afterFinishInternal: function(effect) {
572 if(effect.options.to!=0) return;
573 effect.element.hide().setStyle({opacity: oldOpacity});
574 }}, arguments[1] || {});
575 return new Effect.Opacity(element,options);
578 Effect.Appear = function(element) {
579 element = $(element);
580 var options = Object.extend({
581 from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
582 to: 1.0,
583 // force Safari to render floated elements properly
584 afterFinishInternal: function(effect) {
585 effect.element.forceRerendering();
587 beforeSetup: function(effect) {
588 effect.element.setOpacity(effect.options.from).show();
589 }}, arguments[1] || {});
590 return new Effect.Opacity(element,options);
593 Effect.Puff = function(element) {
594 element = $(element);
595 var oldStyle = {
596 opacity: element.getInlineOpacity(),
597 position: element.getStyle('position'),
598 top: element.style.top,
599 left: element.style.left,
600 width: element.style.width,
601 height: element.style.height
603 return new Effect.Parallel(
604 [ new Effect.Scale(element, 200,
605 { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
606 new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
607 Object.extend({ duration: 1.0,
608 beforeSetupInternal: function(effect) {
609 Position.absolutize(effect.effects[0].element)
611 afterFinishInternal: function(effect) {
612 effect.effects[0].element.hide().setStyle(oldStyle); }
613 }, arguments[1] || {})
617 Effect.BlindUp = function(element) {
618 element = $(element);
619 element.makeClipping();
620 return new Effect.Scale(element, 0,
621 Object.extend({ scaleContent: false,
622 scaleX: false,
623 restoreAfterFinish: true,
624 afterFinishInternal: function(effect) {
625 effect.element.hide().undoClipping();
627 }, arguments[1] || {})
631 Effect.BlindDown = function(element) {
632 element = $(element);
633 var elementDimensions = element.getDimensions();
634 return new Effect.Scale(element, 100, Object.extend({
635 scaleContent: false,
636 scaleX: false,
637 scaleFrom: 0,
638 scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
639 restoreAfterFinish: true,
640 afterSetup: function(effect) {
641 effect.element.makeClipping().setStyle({height: '0px'}).show();
643 afterFinishInternal: function(effect) {
644 effect.element.undoClipping();
646 }, arguments[1] || {}));
649 Effect.SwitchOff = function(element) {
650 element = $(element);
651 var oldOpacity = element.getInlineOpacity();
652 return new Effect.Appear(element, Object.extend({
653 duration: 0.4,
654 from: 0,
655 transition: Effect.Transitions.flicker,
656 afterFinishInternal: function(effect) {
657 new Effect.Scale(effect.element, 1, {
658 duration: 0.3, scaleFromCenter: true,
659 scaleX: false, scaleContent: false, restoreAfterFinish: true,
660 beforeSetup: function(effect) {
661 effect.element.makePositioned().makeClipping();
663 afterFinishInternal: function(effect) {
664 effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
668 }, arguments[1] || {}));
671 Effect.DropOut = function(element) {
672 element = $(element);
673 var oldStyle = {
674 top: element.getStyle('top'),
675 left: element.getStyle('left'),
676 opacity: element.getInlineOpacity() };
677 return new Effect.Parallel(
678 [ new Effect.Move(element, {x: 0, y: 100, sync: true }),
679 new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
680 Object.extend(
681 { duration: 0.5,
682 beforeSetup: function(effect) {
683 effect.effects[0].element.makePositioned();
685 afterFinishInternal: function(effect) {
686 effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
688 }, arguments[1] || {}));
691 Effect.Shake = function(element) {
692 element = $(element);
693 var oldStyle = {
694 top: element.getStyle('top'),
695 left: element.getStyle('left') };
696 return new Effect.Move(element,
697 { x: 20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
698 new Effect.Move(effect.element,
699 { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
700 new Effect.Move(effect.element,
701 { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
702 new Effect.Move(effect.element,
703 { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
704 new Effect.Move(effect.element,
705 { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
706 new Effect.Move(effect.element,
707 { x: -20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
708 effect.element.undoPositioned().setStyle(oldStyle);
709 }}) }}) }}) }}) }}) }});
712 Effect.SlideDown = function(element) {
713 element = $(element).cleanWhitespace();
714 // SlideDown need to have the content of the element wrapped in a container element with fixed height!
715 var oldInnerBottom = element.down().getStyle('bottom');
716 var elementDimensions = element.getDimensions();
717 return new Effect.Scale(element, 100, Object.extend({
718 scaleContent: false,
719 scaleX: false,
720 scaleFrom: window.opera ? 0 : 1,
721 scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
722 restoreAfterFinish: true,
723 afterSetup: function(effect) {
724 effect.element.makePositioned();
725 effect.element.down().makePositioned();
726 if(window.opera) effect.element.setStyle({top: ''});
727 effect.element.makeClipping().setStyle({height: '0px'}).show();
729 afterUpdateInternal: function(effect) {
730 effect.element.down().setStyle({bottom:
731 (effect.dims[0] - effect.element.clientHeight) + 'px' });
733 afterFinishInternal: function(effect) {
734 effect.element.undoClipping().undoPositioned();
735 effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
736 }, arguments[1] || {})
740 Effect.SlideUp = function(element) {
741 element = $(element).cleanWhitespace();
742 var oldInnerBottom = element.down().getStyle('bottom');
743 return new Effect.Scale(element, window.opera ? 0 : 1,
744 Object.extend({ scaleContent: false,
745 scaleX: false,
746 scaleMode: 'box',
747 scaleFrom: 100,
748 restoreAfterFinish: true,
749 beforeStartInternal: function(effect) {
750 effect.element.makePositioned();
751 effect.element.down().makePositioned();
752 if(window.opera) effect.element.setStyle({top: ''});
753 effect.element.makeClipping().show();
755 afterUpdateInternal: function(effect) {
756 effect.element.down().setStyle({bottom:
757 (effect.dims[0] - effect.element.clientHeight) + 'px' });
759 afterFinishInternal: function(effect) {
760 effect.element.hide().undoClipping().undoPositioned().setStyle({bottom: oldInnerBottom});
761 effect.element.down().undoPositioned();
763 }, arguments[1] || {})
767 // Bug in opera makes the TD containing this element expand for a instance after finish
768 Effect.Squish = function(element) {
769 return new Effect.Scale(element, window.opera ? 1 : 0, {
770 restoreAfterFinish: true,
771 beforeSetup: function(effect) {
772 effect.element.makeClipping();
774 afterFinishInternal: function(effect) {
775 effect.element.hide().undoClipping();
780 Effect.Grow = function(element) {
781 element = $(element);
782 var options = Object.extend({
783 direction: 'center',
784 moveTransition: Effect.Transitions.sinoidal,
785 scaleTransition: Effect.Transitions.sinoidal,
786 opacityTransition: Effect.Transitions.full
787 }, arguments[1] || {});
788 var oldStyle = {
789 top: element.style.top,
790 left: element.style.left,
791 height: element.style.height,
792 width: element.style.width,
793 opacity: element.getInlineOpacity() };
795 var dims = element.getDimensions();
796 var initialMoveX, initialMoveY;
797 var moveX, moveY;
799 switch (options.direction) {
800 case 'top-left':
801 initialMoveX = initialMoveY = moveX = moveY = 0;
802 break;
803 case 'top-right':
804 initialMoveX = dims.width;
805 initialMoveY = moveY = 0;
806 moveX = -dims.width;
807 break;
808 case 'bottom-left':
809 initialMoveX = moveX = 0;
810 initialMoveY = dims.height;
811 moveY = -dims.height;
812 break;
813 case 'bottom-right':
814 initialMoveX = dims.width;
815 initialMoveY = dims.height;
816 moveX = -dims.width;
817 moveY = -dims.height;
818 break;
819 case 'center':
820 initialMoveX = dims.width / 2;
821 initialMoveY = dims.height / 2;
822 moveX = -dims.width / 2;
823 moveY = -dims.height / 2;
824 break;
827 return new Effect.Move(element, {
828 x: initialMoveX,
829 y: initialMoveY,
830 duration: 0.01,
831 beforeSetup: function(effect) {
832 effect.element.hide().makeClipping().makePositioned();
834 afterFinishInternal: function(effect) {
835 new Effect.Parallel(
836 [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
837 new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
838 new Effect.Scale(effect.element, 100, {
839 scaleMode: { originalHeight: dims.height, originalWidth: dims.width },
840 sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
841 ], Object.extend({
842 beforeSetup: function(effect) {
843 effect.effects[0].element.setStyle({height: '0px'}).show();
845 afterFinishInternal: function(effect) {
846 effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle);
848 }, options)
854 Effect.Shrink = function(element) {
855 element = $(element);
856 var options = Object.extend({
857 direction: 'center',
858 moveTransition: Effect.Transitions.sinoidal,
859 scaleTransition: Effect.Transitions.sinoidal,
860 opacityTransition: Effect.Transitions.none
861 }, arguments[1] || {});
862 var oldStyle = {
863 top: element.style.top,
864 left: element.style.left,
865 height: element.style.height,
866 width: element.style.width,
867 opacity: element.getInlineOpacity() };
869 var dims = element.getDimensions();
870 var moveX, moveY;
872 switch (options.direction) {
873 case 'top-left':
874 moveX = moveY = 0;
875 break;
876 case 'top-right':
877 moveX = dims.width;
878 moveY = 0;
879 break;
880 case 'bottom-left':
881 moveX = 0;
882 moveY = dims.height;
883 break;
884 case 'bottom-right':
885 moveX = dims.width;
886 moveY = dims.height;
887 break;
888 case 'center':
889 moveX = dims.width / 2;
890 moveY = dims.height / 2;
891 break;
894 return new Effect.Parallel(
895 [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
896 new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
897 new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
898 ], Object.extend({
899 beforeStartInternal: function(effect) {
900 effect.effects[0].element.makePositioned().makeClipping();
902 afterFinishInternal: function(effect) {
903 effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
904 }, options)
908 Effect.Pulsate = function(element) {
909 element = $(element);
910 var options = arguments[1] || {};
911 var oldOpacity = element.getInlineOpacity();
912 var transition = options.transition || Effect.Transitions.sinoidal;
913 var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) };
914 reverser.bind(transition);
915 return new Effect.Opacity(element,
916 Object.extend(Object.extend({ duration: 2.0, from: 0,
917 afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
918 }, options), {transition: reverser}));
921 Effect.Fold = function(element) {
922 element = $(element);
923 var oldStyle = {
924 top: element.style.top,
925 left: element.style.left,
926 width: element.style.width,
927 height: element.style.height };
928 element.makeClipping();
929 return new Effect.Scale(element, 5, Object.extend({
930 scaleContent: false,
931 scaleX: false,
932 afterFinishInternal: function(effect) {
933 new Effect.Scale(element, 1, {
934 scaleContent: false,
935 scaleY: false,
936 afterFinishInternal: function(effect) {
937 effect.element.hide().undoClipping().setStyle(oldStyle);
938 } });
939 }}, arguments[1] || {}));
942 Effect.Morph = Class.create();
943 Object.extend(Object.extend(Effect.Morph.prototype, Effect.Base.prototype), {
944 initialize: function(element) {
945 this.element = $(element);
946 if(!this.element) throw(Effect._elementDoesNotExistError);
947 var options = Object.extend({
948 style: ''
949 }, arguments[1] || {});
950 this.start(options);
952 setup: function(){
953 function parseColor(color){
954 if(!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
955 color = color.parseColor();
956 return $R(0,2).map(function(i){
957 return parseInt( color.slice(i*2+1,i*2+3), 16 )
960 this.transforms = this.options.style.parseStyle().map(function(property){
961 var originalValue = this.element.getStyle(property[0]);
962 return $H({
963 style: property[0],
964 originalValue: property[1].unit=='color' ?
965 parseColor(originalValue) : parseFloat(originalValue || 0),
966 targetValue: property[1].unit=='color' ?
967 parseColor(property[1].value) : property[1].value,
968 unit: property[1].unit
970 }.bind(this)).reject(function(transform){
971 return (
972 (transform.originalValue == transform.targetValue) ||
974 transform.unit != 'color' &&
975 (isNaN(transform.originalValue) || isNaN(transform.targetValue))
980 update: function(position) {
981 var style = $H(), value = null;
982 this.transforms.each(function(transform){
983 value = transform.unit=='color' ?
984 $R(0,2).inject('#',function(m,v,i){
985 return m+(Math.round(transform.originalValue[i]+
986 (transform.targetValue[i] - transform.originalValue[i])*position)).toColorPart() }) :
987 transform.originalValue + Math.round(
988 ((transform.targetValue - transform.originalValue) * position) * 1000)/1000 + transform.unit;
989 style[transform.style] = value;
991 this.element.setStyle(style);
995 Effect.Transform = Class.create();
996 Object.extend(Effect.Transform.prototype, {
997 initialize: function(tracks){
998 this.tracks = [];
999 this.options = arguments[1] || {};
1000 this.addTracks(tracks);
1002 addTracks: function(tracks){
1003 tracks.each(function(track){
1004 var data = $H(track).values().first();
1005 this.tracks.push($H({
1006 ids: $H(track).keys().first(),
1007 effect: Effect.Morph,
1008 options: { style: data }
1009 }));
1010 }.bind(this));
1011 return this;
1013 play: function(){
1014 return new Effect.Parallel(
1015 this.tracks.map(function(track){
1016 var elements = [$(track.ids) || $$(track.ids)].flatten();
1017 return elements.map(function(e){ return new track.effect(e, Object.extend({ sync:true }, track.options)) });
1018 }).flatten(),
1019 this.options
1024 Element.CSS_PROPERTIES = ['azimuth', 'backgroundAttachment', 'backgroundColor', 'backgroundImage',
1025 'backgroundPosition', 'backgroundRepeat', 'borderBottomColor', 'borderBottomStyle',
1026 'borderBottomWidth', 'borderCollapse', 'borderLeftColor', 'borderLeftStyle', 'borderLeftWidth',
1027 'borderRightColor', 'borderRightStyle', 'borderRightWidth', 'borderSpacing', 'borderTopColor',
1028 'borderTopStyle', 'borderTopWidth', 'bottom', 'captionSide', 'clear', 'clip', 'color', 'content',
1029 'counterIncrement', 'counterReset', 'cssFloat', 'cueAfter', 'cueBefore', 'cursor', 'direction',
1030 'display', 'elevation', 'emptyCells', 'fontFamily', 'fontSize', 'fontSizeAdjust', 'fontStretch',
1031 'fontStyle', 'fontVariant', 'fontWeight', 'height', 'left', 'letterSpacing', 'lineHeight',
1032 'listStyleImage', 'listStylePosition', 'listStyleType', 'marginBottom', 'marginLeft', 'marginRight',
1033 'marginTop', 'markerOffset', 'marks', 'maxHeight', 'maxWidth', 'minHeight', 'minWidth', 'opacity',
1034 'orphans', 'outlineColor', 'outlineOffset', 'outlineStyle', 'outlineWidth', 'overflowX', 'overflowY',
1035 'paddingBottom', 'paddingLeft', 'paddingRight', 'paddingTop', 'page', 'pageBreakAfter', 'pageBreakBefore',
1036 'pageBreakInside', 'pauseAfter', 'pauseBefore', 'pitch', 'pitchRange', 'position', 'quotes',
1037 'richness', 'right', 'size', 'speakHeader', 'speakNumeral', 'speakPunctuation', 'speechRate', 'stress',
1038 'tableLayout', 'textAlign', 'textDecoration', 'textIndent', 'textShadow', 'textTransform', 'top',
1039 'unicodeBidi', 'verticalAlign', 'visibility', 'voiceFamily', 'volume', 'whiteSpace', 'widows',
1040 'width', 'wordSpacing', 'zIndex'];
1042 Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
1044 String.prototype.parseStyle = function(){
1045 var element = Element.extend(document.createElement('div'));
1046 element.innerHTML = '<div style="' + this + '"></div>';
1047 var style = element.down().style, styleRules = $H();
1049 Element.CSS_PROPERTIES.each(function(property){
1050 if(style[property]) styleRules[property] = style[property];
1053 var result = $H();
1055 styleRules.each(function(pair){
1056 var property = pair[0], value = pair[1], unit = null;
1058 if(value.parseColor('#zzzzzz') != '#zzzzzz') {
1059 value = value.parseColor();
1060 unit = 'color';
1061 } else if(Element.CSS_LENGTH.test(value))
1062 var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/),
1063 value = parseFloat(components[1]), unit = (components.length == 3) ? components[2] : null;
1065 result[property.underscore().dasherize()] = $H({ value:value, unit:unit });
1066 }.bind(this));
1068 return result;
1071 Element.morph = function(element, style) {
1072 new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || {}));
1073 return element;
1076 ['setOpacity','getOpacity','getInlineOpacity','forceRerendering','setContentZoom',
1077 'collectTextNodes','collectTextNodesIgnoreClass','morph'].each(
1078 function(f) { Element.Methods[f] = Element[f]; }
1081 Element.Methods.visualEffect = function(element, effect, options) {
1082 s = effect.gsub(/_/, '-').camelize();
1083 effect_class = s.charAt(0).toUpperCase() + s.substring(1);
1084 new Effect[effect_class](element, options);
1085 return $(element);
1088 Element.addMethods();