1// script.aculo.us effects.js v1.7.0, Fri Jan 19 19:16:36 CET 2007 2 3// Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) 4// Contributors: 5// Justin Palmer (http://encytemedia.com/) 6// Mark Pilgrim (http://diveintomark.org/) 7// Martin Bialasinki 8// 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/ 11 12// converts rgb() and #xxx to #xxxxxx format, 13// returns self (or first argument) if not convertable 14String.prototype.parseColor = function() { 15 var color = '#'; 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); 19 } else { 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(); 23 } 24 } 25 return(color.length==7 ? color : (arguments[0] || this)); 26} 27 28/*--------------------------------------------------------------------------*/ 29 30Element.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(''); 35} 36 37Element.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(''); 43} 44 45Element.setContentZoom = function(element, percent) { 46 element = $(element); 47 element.setStyle({fontSize: (percent/100) + 'em'}); 48 if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); 49 return element; 50} 51 52Element.getOpacity = function(element){ 53 return $(element).getStyle('opacity'); 54} 55 56Element.setOpacity = function(element, value){ 57 return $(element).setStyle({opacity:value}); 58} 59 60Element.getInlineOpacity = function(element){ 61 return $(element).style.opacity || ''; 62} 63 64Element.forceRerendering = function(element) { 65 try { 66 element = $(element); 67 var n = document.createTextNode(' '); 68 element.appendChild(n); 69 element.removeChild(n); 70 } catch(e) { } 71}; 72 73/*--------------------------------------------------------------------------*/ 74 75Array.prototype.call = function() { 76 var args = arguments; 77 this.each(function(f){ f.apply(this, args) }); 78} 79 80/*--------------------------------------------------------------------------*/ 81 82var Effect = { 83 _elementDoesNotExistError: { 84 name: 'ElementDoesNotExistError', 85 message: 'The specified DOM element does not exist, but is required for this effect to operate' 86 }, 87 tagifyText: function(element) { 88 if(typeof Builder == 'undefined') 89 throw("Effect.tagifyText requires including script.aculo.us' builder.js library"); 90 91 var tagifyStyle = 'position:relative'; 92 if(/MSIE/.test(navigator.userAgent) && !window.opera) tagifyStyle += ';zoom:1'; 93 94 element = $(element); 95 $A(element.childNodes).each( function(child) { 96 if(child.nodeType==3) { 97 child.nodeValue.toArray().each( function(character) { 98 element.insertBefore( 99 Builder.node('span',{style: tagifyStyle}, 100 character == ' ' ? String.fromCharCode(160) : character), 101 child); 102 }); 103 Element.remove(child); 104 } 105 }); 106 }, 107 multiple: function(element, effect) { 108 var elements; 109 if(((typeof element == 'object') || 110 (typeof element == 'function')) && 111 (element.length)) 112 elements = element; 113 else 114 elements = $(element).childNodes; 115 116 var options = Object.extend({ 117 speed: 0.1, 118 delay: 0.0 119 }, arguments[2] || {}); 120 var masterDelay = options.delay; 121 122 $A(elements).each( function(element, index) { 123 new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay })); 124 }); 125 }, 126 PAIRS: { 127 'slide': ['SlideDown','SlideUp'], 128 'blind': ['BlindDown','BlindUp'], 129 'appear': ['Appear','Fade'] 130 }, 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); 139 } 140}; 141 142var Effect2 = Effect; // deprecated 143 144/* ------------- transitions ------------- */ 145 146Effect.Transitions = { 147 linear: Prototype.K, 148 sinoidal: function(pos) { 149 return (-Math.cos(pos*Math.PI)/2) + 0.5; 150 }, 151 reverse: function(pos) { 152 return 1-pos; 153 }, 154 flicker: function(pos) { 155 return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4; 156 }, 157 wobble: function(pos) { 158 return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5; 159 }, 160 pulse: function(pos, pulses) { 161 pulses = pulses || 5; 162 return ( 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)) 166 ); 167 }, 168 none: function(pos) { 169 return 0; 170 }, 171 full: function(pos) { 172 return 1; 173 } 174}; 175 176/* ------------- core effects ------------- */ 177 178Effect.ScopedQueue = Class.create(); 179Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), { 180 initialize: function() { 181 this.effects = []; 182 this.interval = null; 183 }, 184 _each: function(iterator) { 185 this.effects._each(iterator); 186 }, 187 add: function(effect) { 188 var timestamp = new Date().getTime(); 189 190 var position = (typeof effect.options.queue == 'string') ? 191 effect.options.queue : effect.options.queue.position; 192 193 switch(position) { 194 case 'front': 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; 199 }); 200 break; 201 case 'with-last': 202 timestamp = this.effects.pluck('startOn').max() || timestamp; 203 break; 204 case 'end': 205 // start effect after last queued effect has finished 206 timestamp = this.effects.pluck('finishOn').max() || timestamp; 207 break; 208 } 209 210 effect.startOn += timestamp; 211 effect.finishOn += timestamp; 212 213 if(!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit)) 214 this.effects.push(effect); 215 216 if(!this.interval) 217 this.interval = setInterval(this.loop.bind(this), 15); 218 }, 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; 224 } 225 }, 226 loop: function() { 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); 230 } 231}); 232 233Effect.Queues = { 234 instances: $H(), 235 get: function(queueName) { 236 if(typeof queueName != 'string') return queueName; 237 238 if(!this.instances[queueName]) 239 this.instances[queueName] = new Effect.ScopedQueue(); 240 241 return this.instances[queueName]; 242 } 243} 244Effect.Queue = Effect.Queues.get('global'); 245 246Effect.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 251 from: 0.0, 252 to: 1.0, 253 delay: 0.0, 254 queue: 'parallel' 255} 256 257Effect.Base = function() {}; 258Effect.Base.prototype = { 259 position: null, 260 start: function(options) { 261 this.options = Object.extend(Object.extend({},Effect.DefaultOptions), options || {}); 262 this.currentFrame = 0; 263 this.state = 'idle'; 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); 270 }, 271 loop: function(timePos) { 272 if(timePos >= this.startOn) { 273 if(timePos >= this.finishOn) { 274 this.render(1.0); 275 this.cancel(); 276 this.event('beforeFinish'); 277 if(this.finish) this.finish(); 278 this.event('afterFinish'); 279 return; 280 } 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) { 284 this.render(pos); 285 this.currentFrame = frame; 286 } 287 } 288 }, 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'); 295 } 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; 300 this.position = pos; 301 this.event('beforeUpdate'); 302 if(this.update) this.update(pos); 303 this.event('afterUpdate'); 304 } 305 }, 306 cancel: function() { 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'; 311 }, 312 event: function(eventName) { 313 if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this); 314 if(this.options[eventName]) this.options[eventName](this); 315 }, 316 inspect: function() { 317 var data = $H(); 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() + '>'; 321 } 322} 323 324Effect.Parallel = Class.create(); 325Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), { 326 initialize: function(effects) { 327 this.effects = effects || []; 328 this.start(arguments[1]); 329 }, 330 update: function(position) { 331 this.effects.invoke('render', position); 332 }, 333 finish: function(position) { 334 this.effects.each( function(effect) { 335 effect.render(1.0); 336 effect.cancel(); 337 effect.event('beforeFinish'); 338 if(effect.finish) effect.finish(position); 339 effect.event('afterFinish'); 340 }); 341 } 342}); 343 344Effect.Event = Class.create(); 345Object.extend(Object.extend(Effect.Event.prototype, Effect.Base.prototype), { 346 initialize: function() { 347 var options = Object.extend({ 348 duration: 0 349 }, arguments[0] || {}); 350 this.start(options); 351 }, 352 update: Prototype.emptyFunction 353}); 354 355Effect.Opacity = Class.create(); 356Object.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, 365 to: 1.0 366 }, arguments[1] || {}); 367 this.start(options); 368 }, 369 update: function(position) { 370 this.element.setOpacity(position); 371 } 372}); 373 374Effect.Move = Class.create(); 375Object.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({ 380 x: 0, 381 y: 0, 382 mode: 'relative' 383 }, arguments[1] || {}); 384 this.start(options); 385 }, 386 setup: function() { 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; 398 } 399 }, 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' 404 }); 405 } 406}); 407 408// for backwards compatibility 409Effect.MoveBy = function(element, toTop, toLeft) { 410 return new Effect.Move(element, 411 Object.extend({ x: toLeft, y: toTop }, arguments[3] || {})); 412}; 413 414Effect.Scale = Class.create(); 415Object.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({ 420 scaleX: true, 421 scaleY: true, 422 scaleContent: true, 423 scaleFromCenter: false, 424 scaleMode: 'box', // 'box' or 'contents' or {} with provided values 425 scaleFrom: 100.0, 426 scaleTo: percent 427 }, arguments[2] || {}); 428 this.start(options); 429 }, 430 setup: function() { 431 this.restoreAfterFinish = this.options.restoreAfterFinish || false; 432 this.elementPositioning = this.element.getStyle('position'); 433 434 this.originalStyle = {}; 435 ['top','left','width','height','fontSize'].each( function(k) { 436 this.originalStyle[k] = this.element.style[k]; 437 }.bind(this)); 438 439 this.originalTop = this.element.offsetTop; 440 this.originalLeft = this.element.offsetLeft; 441 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; 447 } 448 }.bind(this)); 449 450 this.factor = (this.options.scaleTo - this.options.scaleFrom)/100; 451 452 this.dims = null; 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]; 457 if(!this.dims) 458 this.dims = [this.options.scaleMode.originalHeight, 459 this.options.scaleMode.originalWidth]; 460 }, 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); 466 }, 467 finish: function(position) { 468 if(this.restoreAfterFinish) this.element.setStyle(this.originalStyle); 469 }, 470 setDimensions: function(height, width) { 471 var d = {}; 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'; 480 } else { 481 if(this.options.scaleY) d.top = -topd + 'px'; 482 if(this.options.scaleX) d.left = -leftd + 'px'; 483 } 484 } 485 this.element.setStyle(d); 486 } 487}); 488 489Effect.Highlight = Class.create(); 490Object.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] || {}); 495 this.start(options); 496 }, 497 setup: function() { 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 501 this.oldStyle = {}; 502 if (!this.options.keepBackgroundImage) { 503 this.oldStyle.backgroundImage = this.element.getStyle('background-image'); 504 this.element.setStyle({backgroundImage: 'none'}); 505 } 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)); 513 }, 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)) }); 517 }, 518 finish: function() { 519 this.element.setStyle(Object.extend(this.oldStyle, { 520 backgroundColor: this.options.restorecolor 521 })); 522 } 523}); 524 525Effect.ScrollTo = Class.create(); 526Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), { 527 initialize: function(element) { 528 this.element = $(element); 529 this.start(arguments[1] || {}); 530 }, 531 setup: function() { 532 Position.prepare(); 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; 542 }, 543 update: function(position) { 544 Position.prepare(); 545 window.scrollTo(Position.deltaX, 546 this.scrollStart + (position*this.delta)); 547 } 548}); 549 550/* ------------- combination effects ------------- */ 551 552Effect.Fade = function(element) { 553 element = $(element); 554 var oldOpacity = element.getInlineOpacity(); 555 var options = Object.extend({ 556 from: element.getOpacity() || 1.0, 557 to: 0.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); 563} 564 565Effect.Appear = function(element) { 566 element = $(element); 567 var options = Object.extend({ 568 from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0), 569 to: 1.0, 570 // force Safari to render floated elements properly 571 afterFinishInternal: function(effect) { 572 effect.element.forceRerendering(); 573 }, 574 beforeSetup: function(effect) { 575 effect.element.setOpacity(effect.options.from).show(); 576 }}, arguments[1] || {}); 577 return new Effect.Opacity(element,options); 578} 579 580Effect.Puff = function(element) { 581 element = $(element); 582 var oldStyle = { 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 589 }; 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) 597 }, 598 afterFinishInternal: function(effect) { 599 effect.effects[0].element.hide().setStyle(oldStyle); } 600 }, arguments[1] || {}) 601 ); 602} 603 604Effect.BlindUp = function(element) { 605 element = $(element); 606 element.makeClipping(); 607 return new Effect.Scale(element, 0, 608 Object.extend({ scaleContent: false, 609 scaleX: false, 610 restoreAfterFinish: true, 611 afterFinishInternal: function(effect) { 612 effect.element.hide().undoClipping(); 613 } 614 }, arguments[1] || {}) 615 ); 616} 617 618Effect.BlindDown = function(element) { 619 element = $(element); 620 var elementDimensions = element.getDimensions(); 621 return new Effect.Scale(element, 100, Object.extend({ 622 scaleContent: false, 623 scaleX: false, 624 scaleFrom: 0, 625 scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, 626 restoreAfterFinish: true, 627 afterSetup: function(effect) { 628 effect.element.makeClipping().setStyle({height: '0px'}).show(); 629 }, 630 afterFinishInternal: function(effect) { 631 effect.element.undoClipping(); 632 } 633 }, arguments[1] || {})); 634} 635 636Effect.SwitchOff = function(element) { 637 element = $(element); 638 var oldOpacity = element.getInlineOpacity(); 639 return new Effect.Appear(element, Object.extend({ 640 duration: 0.4, 641 from: 0, 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(); 649 }, 650 afterFinishInternal: function(effect) { 651 effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity}); 652 } 653 }) 654 } 655 }, arguments[1] || {})); 656} 657 658Effect.DropOut = function(element) { 659 element = $(element); 660 var oldStyle = { 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 }) ], 667 Object.extend( 668 { duration: 0.5, 669 beforeSetup: function(effect) { 670 effect.effects[0].element.makePositioned(); 671 }, 672 afterFinishInternal: function(effect) { 673 effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle); 674 } 675 }, arguments[1] || {})); 676} 677 678Effect.Shake = function(element) { 679 element = $(element); 680 var oldStyle = { 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 }}) }}) }}) }}) }}) }}); 697} 698 699Effect.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({ 705 scaleContent: false, 706 scaleX: false, 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(); 715 }, 716 afterUpdateInternal: function(effect) { 717 effect.element.down().setStyle({bottom: 718 (effect.dims[0] - effect.element.clientHeight) + 'px' }); 719 }, 720 afterFinishInternal: function(effect) { 721 effect.element.undoClipping().undoPositioned(); 722 effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); } 723 }, arguments[1] || {}) 724 ); 725} 726 727Effect.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, 732 scaleX: false, 733 scaleMode: 'box', 734 scaleFrom: 100, 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(); 741 }, 742 afterUpdateInternal: function(effect) { 743 effect.element.down().setStyle({bottom: 744 (effect.dims[0] - effect.element.clientHeight) + 'px' }); 745 }, 746 afterFinishInternal: function(effect) { 747 effect.element.hide().undoClipping().undoPositioned().setStyle({bottom: oldInnerBottom}); 748 effect.element.down().undoPositioned(); 749 } 750 }, arguments[1] || {}) 751 ); 752} 753 754// Bug in opera makes the TD containing this element expand for a instance after finish 755Effect.Squish = function(element) { 756 return new Effect.Scale(element, window.opera ? 1 : 0, { 757 restoreAfterFinish: true, 758 beforeSetup: function(effect) { 759 effect.element.makeClipping(); 760 }, 761 afterFinishInternal: function(effect) { 762 effect.element.hide().undoClipping(); 763 } 764 }); 765} 766 767Effect.Grow = function(element) { 768 element = $(element); 769 var options = Object.extend({ 770 direction: 'center', 771 moveTransition: Effect.Transitions.sinoidal, 772 scaleTransition: Effect.Transitions.sinoidal, 773 opacityTransition: Effect.Transitions.full 774 }, arguments[1] || {}); 775 var oldStyle = { 776 top: element.style.top, 777 left: element.style.left, 778 height: element.style.height, 779 width: element.style.width, 780 opacity: element.getInlineOpacity() }; 781 782 var dims = element.getDimensions(); 783 var initialMoveX, initialMoveY; 784 var moveX, moveY; 785 786 switch (options.direction) { 787 case 'top-left': 788 initialMoveX = initialMoveY = moveX = moveY = 0; 789 break; 790 case 'top-right': 791 initialMoveX = dims.width; 792 initialMoveY = moveY = 0; 793 moveX = -dims.width; 794 break; 795 case 'bottom-left': 796 initialMoveX = moveX = 0; 797 initialMoveY = dims.height; 798 moveY = -dims.height; 799 break; 800 case 'bottom-right': 801 initialMoveX = dims.width; 802 initialMoveY = dims.height; 803 moveX = -dims.width; 804 moveY = -dims.height; 805 break; 806 case 'center': 807 initialMoveX = dims.width / 2; 808 initialMoveY = dims.height / 2; 809 moveX = -dims.width / 2; 810 moveY = -dims.height / 2; 811 break; 812 } 813 814 return new Effect.Move(element, { 815 x: initialMoveX, 816 y: initialMoveY, 817 duration: 0.01, 818 beforeSetup: function(effect) { 819 effect.element.hide().makeClipping().makePositioned(); 820 }, 821 afterFinishInternal: function(effect) { 822 new Effect.Parallel( 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}) 828 ], Object.extend({ 829 beforeSetup: function(effect) { 830 effect.effects[0].element.setStyle({height: '0px'}).show(); 831 }, 832 afterFinishInternal: function(effect) { 833 effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); 834 } 835 }, options) 836 ) 837 } 838 }); 839} 840 841Effect.Shrink = function(element) { 842 element = $(element); 843 var options = Object.extend({ 844 direction: 'center', 845 moveTransition: Effect.Transitions.sinoidal, 846 scaleTransition: Effect.Transitions.sinoidal, 847 opacityTransition: Effect.Transitions.none 848 }, arguments[1] || {}); 849 var oldStyle = { 850 top: element.style.top, 851 left: element.style.left, 852 height: element.style.height, 853 width: element.style.width, 854 opacity: element.getInlineOpacity() }; 855 856 var dims = element.getDimensions(); 857 var moveX, moveY; 858 859 switch (options.direction) { 860 case 'top-left': 861 moveX = moveY = 0; 862 break; 863 case 'top-right': 864 moveX = dims.width; 865 moveY = 0; 866 break; 867 case 'bottom-left': 868 moveX = 0; 869 moveY = dims.height; 870 break; 871 case 'bottom-right': 872 moveX = dims.width; 873 moveY = dims.height; 874 break; 875 case 'center': 876 moveX = dims.width / 2; 877 moveY = dims.height / 2; 878 break; 879 } 880 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 }) 885 ], Object.extend({ 886 beforeStartInternal: function(effect) { 887 effect.effects[0].element.makePositioned().makeClipping(); 888 }, 889 afterFinishInternal: function(effect) { 890 effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); } 891 }, options) 892 ); 893} 894 895Effect.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})); 906} 907 908Effect.Fold = function(element) { 909 element = $(element); 910 var oldStyle = { 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({ 917 scaleContent: false, 918 scaleX: false, 919 afterFinishInternal: function(effect) { 920 new Effect.Scale(element, 1, { 921 scaleContent: false, 922 scaleY: false, 923 afterFinishInternal: function(effect) { 924 effect.element.hide().undoClipping().setStyle(oldStyle); 925 } }); 926 }}, arguments[1] || {})); 927}; 928 929Effect.Morph = Class.create(); 930Object.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({ 935 style: {} 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; 946 throw $break; 947 } 948 }); 949 if (cssText) throw $break; 950 }); 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()] = ''; 957 }); 958 } 959 } else this.style = options.style.parseStyle(); 960 } else this.style = $H(options.style) 961 this.start(options); 962 }, 963 setup: function(){ 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 ) 969 }); 970 } 971 this.transforms = this.style.map(function(pair){ 972 var property = pair[0].underscore().dasherize(), value = pair[1], unit = null; 973 974 if(value.parseColor('#zzzzzz') != '#zzzzzz') { 975 value = value.parseColor(); 976 unit = 'color'; 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; 984 985 var originalValue = this.element.getStyle(property); 986 return $H({ 987 style: property, 988 originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0), 989 targetValue: unit=='color' ? parseColor(value) : value, 990 unit: unit 991 }); 992 }.bind(this)).reject(function(transform){ 993 return ( 994 (transform.originalValue == transform.targetValue) || 995 ( 996 transform.unit != 'color' && 997 (isNaN(transform.originalValue) || isNaN(transform.targetValue)) 998 ) 999 ) 1000 }); 1001 }, 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; 1012 }); 1013 this.element.setStyle(style); 1014 } 1015}); 1016 1017Effect.Transform = Class.create(); 1018Object.extend(Effect.Transform.prototype, { 1019 initialize: function(tracks){ 1020 this.tracks = []; 1021 this.options = arguments[1] || {}; 1022 this.addTracks(tracks); 1023 }, 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 } 1031 })); 1032 }.bind(this)); 1033 return this; 1034 }, 1035 play: function(){ 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)) }); 1040 }).flatten(), 1041 this.options 1042 ); 1043 } 1044}); 1045 1046Element.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'); 1056 1057Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/; 1058 1059String.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(); 1063 1064 Element.CSS_PROPERTIES.each(function(property){ 1065 if(style[property]) styleRules[property] = style[property]; 1066 }); 1067 if(/MSIE/.test(navigator.userAgent) && !window.opera && this.indexOf('opacity') > -1) { 1068 styleRules.opacity = this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]; 1069 } 1070 return styleRules; 1071}; 1072 1073Element.morph = function(element, style) { 1074 new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || {})); 1075 return element; 1076}; 1077 1078['setOpacity','getOpacity','getInlineOpacity','forceRerendering','setContentZoom', 1079 'collectTextNodes','collectTextNodesIgnoreClass','morph'].each( 1080 function(f) { Element.Methods[f] = Element[f]; } 1081); 1082 1083Element.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); 1087 return $(element); 1088}; 1089 1090Element.addMethods();