effects.js 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123
  1. // script.aculo.us effects.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
  2. // Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
  3. // Contributors:
  4. // Justin Palmer (http://encytemedia.com/)
  5. // Mark Pilgrim (http://diveintomark.org/)
  6. // Martin Bialasinki
  7. //
  8. // script.aculo.us is freely distributable under the terms of an MIT-style license.
  9. // 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();
  21. }
  22. }
  23. return (color.length==7 ? color : (arguments[0] || this));
  24. };
  25. /*--------------------------------------------------------------------------*/
  26. Element.collectTextNodes = function(element) {
  27. return $A($(element).childNodes).collect( function(node) {
  28. return (node.nodeType==3 ? node.nodeValue :
  29. (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
  30. }).flatten().join('');
  31. };
  32. Element.collectTextNodesIgnoreClass = function(element, className) {
  33. return $A($(element).childNodes).collect( function(node) {
  34. return (node.nodeType==3 ? node.nodeValue :
  35. ((node.hasChildNodes() && !Element.hasClassName(node,className)) ?
  36. Element.collectTextNodesIgnoreClass(node, className) : ''));
  37. }).flatten().join('');
  38. };
  39. Element.setContentZoom = function(element, percent) {
  40. element = $(element);
  41. element.setStyle({fontSize: (percent/100) + 'em'});
  42. if (Prototype.Browser.WebKit) window.scrollBy(0,0);
  43. return element;
  44. };
  45. Element.getInlineOpacity = function(element){
  46. return $(element).style.opacity || '';
  47. };
  48. Element.forceRerendering = function(element) {
  49. try {
  50. element = $(element);
  51. var n = document.createTextNode(' ');
  52. element.appendChild(n);
  53. element.removeChild(n);
  54. } catch(e) { }
  55. };
  56. /*--------------------------------------------------------------------------*/
  57. var Effect = {
  58. _elementDoesNotExistError: {
  59. name: 'ElementDoesNotExistError',
  60. message: 'The specified DOM element does not exist, but is required for this effect to operate'
  61. },
  62. Transitions: {
  63. linear: Prototype.K,
  64. sinoidal: function(pos) {
  65. return (-Math.cos(pos*Math.PI)/2) + 0.5;
  66. },
  67. reverse: function(pos) {
  68. return 1-pos;
  69. },
  70. flicker: function(pos) {
  71. var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
  72. return pos > 1 ? 1 : pos;
  73. },
  74. wobble: function(pos) {
  75. return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
  76. },
  77. pulse: function(pos, pulses) {
  78. pulses = pulses || 5;
  79. return (
  80. ((pos % (1/pulses)) * pulses).round() == 0 ?
  81. ((pos * pulses * 2) - (pos * pulses * 2).floor()) :
  82. 1 - ((pos * pulses * 2) - (pos * pulses * 2).floor())
  83. );
  84. },
  85. spring: function(pos) {
  86. return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6));
  87. },
  88. none: function(pos) {
  89. return 0;
  90. },
  91. full: function(pos) {
  92. return 1;
  93. }
  94. },
  95. DefaultOptions: {
  96. duration: 1.0, // seconds
  97. fps: 100, // 100= assume 66fps max.
  98. sync: false, // true for combining
  99. from: 0.0,
  100. to: 1.0,
  101. delay: 0.0,
  102. queue: 'parallel'
  103. },
  104. tagifyText: function(element) {
  105. var tagifyStyle = 'position:relative';
  106. if (Prototype.Browser.IE) tagifyStyle += ';zoom:1';
  107. element = $(element);
  108. $A(element.childNodes).each( function(child) {
  109. if (child.nodeType==3) {
  110. child.nodeValue.toArray().each( function(character) {
  111. element.insertBefore(
  112. new Element('span', {style: tagifyStyle}).update(
  113. character == ' ' ? String.fromCharCode(160) : character),
  114. child);
  115. });
  116. Element.remove(child);
  117. }
  118. });
  119. },
  120. multiple: function(element, effect) {
  121. var elements;
  122. if (((typeof element == 'object') ||
  123. Object.isFunction(element)) &&
  124. (element.length))
  125. elements = element;
  126. else
  127. elements = $(element).childNodes;
  128. var options = Object.extend({
  129. speed: 0.1,
  130. delay: 0.0
  131. }, arguments[2] || { });
  132. var masterDelay = options.delay;
  133. $A(elements).each( function(element, index) {
  134. new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
  135. });
  136. },
  137. PAIRS: {
  138. 'slide': ['SlideDown','SlideUp'],
  139. 'blind': ['BlindDown','BlindUp'],
  140. 'appear': ['Appear','Fade']
  141. },
  142. toggle: function(element, effect) {
  143. element = $(element);
  144. effect = (effect || 'appear').toLowerCase();
  145. var options = Object.extend({
  146. queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
  147. }, arguments[2] || { });
  148. Effect[element.visible() ?
  149. Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
  150. }
  151. };
  152. Effect.DefaultOptions.transition = Effect.Transitions.sinoidal;
  153. /* ------------- core effects ------------- */
  154. Effect.ScopedQueue = Class.create(Enumerable, {
  155. initialize: function() {
  156. this.effects = [];
  157. this.interval = null;
  158. },
  159. _each: function(iterator) {
  160. this.effects._each(iterator);
  161. },
  162. add: function(effect) {
  163. var timestamp = new Date().getTime();
  164. var position = Object.isString(effect.options.queue) ?
  165. effect.options.queue : effect.options.queue.position;
  166. switch(position) {
  167. case 'front':
  168. // move unstarted effects after this effect
  169. this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
  170. e.startOn += effect.finishOn;
  171. e.finishOn += effect.finishOn;
  172. });
  173. break;
  174. case 'with-last':
  175. timestamp = this.effects.pluck('startOn').max() || timestamp;
  176. break;
  177. case 'end':
  178. // start effect after last queued effect has finished
  179. timestamp = this.effects.pluck('finishOn').max() || timestamp;
  180. break;
  181. }
  182. effect.startOn += timestamp;
  183. effect.finishOn += timestamp;
  184. if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
  185. this.effects.push(effect);
  186. if (!this.interval)
  187. this.interval = setInterval(this.loop.bind(this), 15);
  188. },
  189. remove: function(effect) {
  190. this.effects = this.effects.reject(function(e) { return e==effect });
  191. if (this.effects.length == 0) {
  192. clearInterval(this.interval);
  193. this.interval = null;
  194. }
  195. },
  196. loop: function() {
  197. var timePos = new Date().getTime();
  198. for(var i=0, len=this.effects.length;i<len;i++)
  199. this.effects[i] && this.effects[i].loop(timePos);
  200. }
  201. });
  202. Effect.Queues = {
  203. instances: $H(),
  204. get: function(queueName) {
  205. if (!Object.isString(queueName)) return queueName;
  206. return this.instances.get(queueName) ||
  207. this.instances.set(queueName, new Effect.ScopedQueue());
  208. }
  209. };
  210. Effect.Queue = Effect.Queues.get('global');
  211. Effect.Base = Class.create({
  212. position: null,
  213. start: function(options) {
  214. function codeForEvent(options,eventName){
  215. return (
  216. (options[eventName+'Internal'] ? 'this.options.'+eventName+'Internal(this);' : '') +
  217. (options[eventName] ? 'this.options.'+eventName+'(this);' : '')
  218. );
  219. }
  220. if (options && options.transition === false) options.transition = Effect.Transitions.linear;
  221. this.options = Object.extend(Object.extend({ },Effect.DefaultOptions), options || { });
  222. this.currentFrame = 0;
  223. this.state = 'idle';
  224. this.startOn = this.options.delay*1000;
  225. this.finishOn = this.startOn+(this.options.duration*1000);
  226. this.fromToDelta = this.options.to-this.options.from;
  227. this.totalTime = this.finishOn-this.startOn;
  228. this.totalFrames = this.options.fps*this.options.duration;
  229. eval('this.render = function(pos){ '+
  230. 'if (this.state=="idle"){this.state="running";'+
  231. codeForEvent(this.options,'beforeSetup')+
  232. (this.setup ? 'this.setup();':'')+
  233. codeForEvent(this.options,'afterSetup')+
  234. '};if (this.state=="running"){'+
  235. 'pos=this.options.transition(pos)*'+this.fromToDelta+'+'+this.options.from+';'+
  236. 'this.position=pos;'+
  237. codeForEvent(this.options,'beforeUpdate')+
  238. (this.update ? 'this.update(pos);':'')+
  239. codeForEvent(this.options,'afterUpdate')+
  240. '}}');
  241. this.event('beforeStart');
  242. if (!this.options.sync)
  243. Effect.Queues.get(Object.isString(this.options.queue) ?
  244. 'global' : this.options.queue.scope).add(this);
  245. },
  246. loop: function(timePos) {
  247. if (timePos >= this.startOn) {
  248. if (timePos >= this.finishOn) {
  249. this.render(1.0);
  250. this.cancel();
  251. this.event('beforeFinish');
  252. if (this.finish) this.finish();
  253. this.event('afterFinish');
  254. return;
  255. }
  256. var pos = (timePos - this.startOn) / this.totalTime,
  257. frame = (pos * this.totalFrames).round();
  258. if (frame > this.currentFrame) {
  259. this.render(pos);
  260. this.currentFrame = frame;
  261. }
  262. }
  263. },
  264. cancel: function() {
  265. if (!this.options.sync)
  266. Effect.Queues.get(Object.isString(this.options.queue) ?
  267. 'global' : this.options.queue.scope).remove(this);
  268. this.state = 'finished';
  269. },
  270. event: function(eventName) {
  271. if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
  272. if (this.options[eventName]) this.options[eventName](this);
  273. },
  274. inspect: function() {
  275. var data = $H();
  276. for(property in this)
  277. if (!Object.isFunction(this[property])) data.set(property, this[property]);
  278. return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';
  279. }
  280. });
  281. Effect.Parallel = Class.create(Effect.Base, {
  282. initialize: function(effects) {
  283. this.effects = effects || [];
  284. this.start(arguments[1]);
  285. },
  286. update: function(position) {
  287. this.effects.invoke('render', position);
  288. },
  289. finish: function(position) {
  290. this.effects.each( function(effect) {
  291. effect.render(1.0);
  292. effect.cancel();
  293. effect.event('beforeFinish');
  294. if (effect.finish) effect.finish(position);
  295. effect.event('afterFinish');
  296. });
  297. }
  298. });
  299. Effect.Tween = Class.create(Effect.Base, {
  300. initialize: function(object, from, to) {
  301. object = Object.isString(object) ? $(object) : object;
  302. var args = $A(arguments), method = args.last(),
  303. options = args.length == 5 ? args[3] : null;
  304. this.method = Object.isFunction(method) ? method.bind(object) :
  305. Object.isFunction(object[method]) ? object[method].bind(object) :
  306. function(value) { object[method] = value };
  307. this.start(Object.extend({ from: from, to: to }, options || { }));
  308. },
  309. update: function(position) {
  310. this.method(position);
  311. }
  312. });
  313. Effect.Event = Class.create(Effect.Base, {
  314. initialize: function() {
  315. this.start(Object.extend({ duration: 0 }, arguments[0] || { }));
  316. },
  317. update: Prototype.emptyFunction
  318. });
  319. Effect.Opacity = Class.create(Effect.Base, {
  320. initialize: function(element) {
  321. this.element = $(element);
  322. if (!this.element) throw(Effect._elementDoesNotExistError);
  323. // make this work on IE on elements without 'layout'
  324. if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
  325. this.element.setStyle({zoom: 1});
  326. var options = Object.extend({
  327. from: this.element.getOpacity() || 0.0,
  328. to: 1.0
  329. }, arguments[1] || { });
  330. this.start(options);
  331. },
  332. update: function(position) {
  333. this.element.setOpacity(position);
  334. }
  335. });
  336. Effect.Move = Class.create(Effect.Base, {
  337. initialize: function(element) {
  338. this.element = $(element);
  339. if (!this.element) throw(Effect._elementDoesNotExistError);
  340. var options = Object.extend({
  341. x: 0,
  342. y: 0,
  343. mode: 'relative'
  344. }, arguments[1] || { });
  345. this.start(options);
  346. },
  347. setup: function() {
  348. this.element.makePositioned();
  349. this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
  350. this.originalTop = parseFloat(this.element.getStyle('top') || '0');
  351. if (this.options.mode == 'absolute') {
  352. this.options.x = this.options.x - this.originalLeft;
  353. this.options.y = this.options.y - this.originalTop;
  354. }
  355. },
  356. update: function(position) {
  357. this.element.setStyle({
  358. left: (this.options.x * position + this.originalLeft).round() + 'px',
  359. top: (this.options.y * position + this.originalTop).round() + 'px'
  360. });
  361. }
  362. });
  363. // for backwards compatibility
  364. Effect.MoveBy = function(element, toTop, toLeft) {
  365. return new Effect.Move(element,
  366. Object.extend({ x: toLeft, y: toTop }, arguments[3] || { }));
  367. };
  368. Effect.Scale = Class.create(Effect.Base, {
  369. initialize: function(element, percent) {
  370. this.element = $(element);
  371. if (!this.element) throw(Effect._elementDoesNotExistError);
  372. var options = Object.extend({
  373. scaleX: true,
  374. scaleY: true,
  375. scaleContent: true,
  376. scaleFromCenter: false,
  377. scaleMode: 'box', // 'box' or 'contents' or { } with provided values
  378. scaleFrom: 100.0,
  379. scaleTo: percent
  380. }, arguments[2] || { });
  381. this.start(options);
  382. },
  383. setup: function() {
  384. this.restoreAfterFinish = this.options.restoreAfterFinish || false;
  385. this.elementPositioning = this.element.getStyle('position');
  386. this.originalStyle = { };
  387. ['top','left','width','height','fontSize'].each( function(k) {
  388. this.originalStyle[k] = this.element.style[k];
  389. }.bind(this));
  390. this.originalTop = this.element.offsetTop;
  391. this.originalLeft = this.element.offsetLeft;
  392. var fontSize = this.element.getStyle('font-size') || '100%';
  393. ['em','px','%','pt'].each( function(fontSizeType) {
  394. if (fontSize.indexOf(fontSizeType)>0) {
  395. this.fontSize = parseFloat(fontSize);
  396. this.fontSizeType = fontSizeType;
  397. }
  398. }.bind(this));
  399. this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
  400. this.dims = null;
  401. if (this.options.scaleMode=='box')
  402. this.dims = [this.element.offsetHeight, this.element.offsetWidth];
  403. if (/^content/.test(this.options.scaleMode))
  404. this.dims = [this.element.scrollHeight, this.element.scrollWidth];
  405. if (!this.dims)
  406. this.dims = [this.options.scaleMode.originalHeight,
  407. this.options.scaleMode.originalWidth];
  408. },
  409. update: function(position) {
  410. var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
  411. if (this.options.scaleContent && this.fontSize)
  412. this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
  413. this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
  414. },
  415. finish: function(position) {
  416. if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
  417. },
  418. setDimensions: function(height, width) {
  419. var d = { };
  420. if (this.options.scaleX) d.width = width.round() + 'px';
  421. if (this.options.scaleY) d.height = height.round() + 'px';
  422. if (this.options.scaleFromCenter) {
  423. var topd = (height - this.dims[0])/2;
  424. var leftd = (width - this.dims[1])/2;
  425. if (this.elementPositioning == 'absolute') {
  426. if (this.options.scaleY) d.top = this.originalTop-topd + 'px';
  427. if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
  428. } else {
  429. if (this.options.scaleY) d.top = -topd + 'px';
  430. if (this.options.scaleX) d.left = -leftd + 'px';
  431. }
  432. }
  433. this.element.setStyle(d);
  434. }
  435. });
  436. Effect.Highlight = Class.create(Effect.Base, {
  437. initialize: function(element) {
  438. this.element = $(element);
  439. if (!this.element) throw(Effect._elementDoesNotExistError);
  440. var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { });
  441. this.start(options);
  442. },
  443. setup: function() {
  444. // Prevent executing on elements not in the layout flow
  445. if (this.element.getStyle('display')=='none') { this.cancel(); return; }
  446. // Disable background image during the effect
  447. this.oldStyle = { };
  448. if (!this.options.keepBackgroundImage) {
  449. this.oldStyle.backgroundImage = this.element.getStyle('background-image');
  450. this.element.setStyle({backgroundImage: 'none'});
  451. }
  452. if (!this.options.endcolor)
  453. this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
  454. if (!this.options.restorecolor)
  455. this.options.restorecolor = this.element.getStyle('background-color');
  456. // init color calculations
  457. this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
  458. 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));
  459. },
  460. update: function(position) {
  461. this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
  462. return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) });
  463. },
  464. finish: function() {
  465. this.element.setStyle(Object.extend(this.oldStyle, {
  466. backgroundColor: this.options.restorecolor
  467. }));
  468. }
  469. });
  470. Effect.ScrollTo = function(element) {
  471. var options = arguments[1] || { },
  472. scrollOffsets = document.viewport.getScrollOffsets(),
  473. elementOffsets = $(element).cumulativeOffset(),
  474. max = (window.height || document.body.scrollHeight) - document.viewport.getHeight();
  475. if (options.offset) elementOffsets[1] += options.offset;
  476. return new Effect.Tween(null,
  477. scrollOffsets.top,
  478. elementOffsets[1] > max ? max : elementOffsets[1],
  479. options,
  480. function(p){ scrollTo(scrollOffsets.left, p.round()) }
  481. );
  482. };
  483. /* ------------- combination effects ------------- */
  484. Effect.Fade = function(element) {
  485. element = $(element);
  486. var oldOpacity = element.getInlineOpacity();
  487. var options = Object.extend({
  488. from: element.getOpacity() || 1.0,
  489. to: 0.0,
  490. afterFinishInternal: function(effect) {
  491. if (effect.options.to!=0) return;
  492. effect.element.hide().setStyle({opacity: oldOpacity});
  493. }
  494. }, arguments[1] || { });
  495. return new Effect.Opacity(element,options);
  496. };
  497. Effect.Appear = function(element) {
  498. element = $(element);
  499. var options = Object.extend({
  500. from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
  501. to: 1.0,
  502. // force Safari to render floated elements properly
  503. afterFinishInternal: function(effect) {
  504. effect.element.forceRerendering();
  505. },
  506. beforeSetup: function(effect) {
  507. effect.element.setOpacity(effect.options.from).show();
  508. }}, arguments[1] || { });
  509. return new Effect.Opacity(element,options);
  510. };
  511. Effect.Puff = function(element) {
  512. element = $(element);
  513. var oldStyle = {
  514. opacity: element.getInlineOpacity(),
  515. position: element.getStyle('position'),
  516. top: element.style.top,
  517. left: element.style.left,
  518. width: element.style.width,
  519. height: element.style.height
  520. };
  521. return new Effect.Parallel(
  522. [ new Effect.Scale(element, 200,
  523. { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
  524. new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
  525. Object.extend({ duration: 1.0,
  526. beforeSetupInternal: function(effect) {
  527. Position.absolutize(effect.effects[0].element)
  528. },
  529. afterFinishInternal: function(effect) {
  530. effect.effects[0].element.hide().setStyle(oldStyle); }
  531. }, arguments[1] || { })
  532. );
  533. };
  534. Effect.BlindUp = function(element) {
  535. element = $(element);
  536. element.makeClipping();
  537. return new Effect.Scale(element, 0,
  538. Object.extend({ scaleContent: false,
  539. scaleX: false,
  540. restoreAfterFinish: true,
  541. afterFinishInternal: function(effect) {
  542. effect.element.hide().undoClipping();
  543. }
  544. }, arguments[1] || { })
  545. );
  546. };
  547. Effect.BlindDown = function(element) {
  548. element = $(element);
  549. var elementDimensions = element.getDimensions();
  550. return new Effect.Scale(element, 100, Object.extend({
  551. scaleContent: false,
  552. scaleX: false,
  553. scaleFrom: 0,
  554. scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
  555. restoreAfterFinish: true,
  556. afterSetup: function(effect) {
  557. effect.element.makeClipping().setStyle({height: '0px'}).show();
  558. },
  559. afterFinishInternal: function(effect) {
  560. effect.element.undoClipping();
  561. }
  562. }, arguments[1] || { }));
  563. };
  564. Effect.SwitchOff = function(element) {
  565. element = $(element);
  566. var oldOpacity = element.getInlineOpacity();
  567. return new Effect.Appear(element, Object.extend({
  568. duration: 0.4,
  569. from: 0,
  570. transition: Effect.Transitions.flicker,
  571. afterFinishInternal: function(effect) {
  572. new Effect.Scale(effect.element, 1, {
  573. duration: 0.3, scaleFromCenter: true,
  574. scaleX: false, scaleContent: false, restoreAfterFinish: true,
  575. beforeSetup: function(effect) {
  576. effect.element.makePositioned().makeClipping();
  577. },
  578. afterFinishInternal: function(effect) {
  579. effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
  580. }
  581. })
  582. }
  583. }, arguments[1] || { }));
  584. };
  585. Effect.DropOut = function(element) {
  586. element = $(element);
  587. var oldStyle = {
  588. top: element.getStyle('top'),
  589. left: element.getStyle('left'),
  590. opacity: element.getInlineOpacity() };
  591. return new Effect.Parallel(
  592. [ new Effect.Move(element, {x: 0, y: 100, sync: true }),
  593. new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
  594. Object.extend(
  595. { duration: 0.5,
  596. beforeSetup: function(effect) {
  597. effect.effects[0].element.makePositioned();
  598. },
  599. afterFinishInternal: function(effect) {
  600. effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
  601. }
  602. }, arguments[1] || { }));
  603. };
  604. Effect.Shake = function(element) {
  605. element = $(element);
  606. var options = Object.extend({
  607. distance: 20,
  608. duration: 0.5
  609. }, arguments[1] || {});
  610. var distance = parseFloat(options.distance);
  611. var split = parseFloat(options.duration) / 10.0;
  612. var oldStyle = {
  613. top: element.getStyle('top'),
  614. left: element.getStyle('left') };
  615. return new Effect.Move(element,
  616. { x: distance, y: 0, duration: split, afterFinishInternal: function(effect) {
  617. new Effect.Move(effect.element,
  618. { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
  619. new Effect.Move(effect.element,
  620. { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
  621. new Effect.Move(effect.element,
  622. { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
  623. new Effect.Move(effect.element,
  624. { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
  625. new Effect.Move(effect.element,
  626. { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) {
  627. effect.element.undoPositioned().setStyle(oldStyle);
  628. }}) }}) }}) }}) }}) }});
  629. };
  630. Effect.SlideDown = function(element) {
  631. element = $(element).cleanWhitespace();
  632. // SlideDown need to have the content of the element wrapped in a container element with fixed height!
  633. var oldInnerBottom = element.down().getStyle('bottom');
  634. var elementDimensions = element.getDimensions();
  635. return new Effect.Scale(element, 100, Object.extend({
  636. scaleContent: false,
  637. scaleX: false,
  638. scaleFrom: window.opera ? 0 : 1,
  639. scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
  640. restoreAfterFinish: true,
  641. afterSetup: function(effect) {
  642. effect.element.makePositioned();
  643. effect.element.down().makePositioned();
  644. if (window.opera) effect.element.setStyle({top: ''});
  645. effect.element.makeClipping().setStyle({height: '0px'}).show();
  646. },
  647. afterUpdateInternal: function(effect) {
  648. effect.element.down().setStyle({bottom:
  649. (effect.dims[0] - effect.element.clientHeight) + 'px' });
  650. },
  651. afterFinishInternal: function(effect) {
  652. effect.element.undoClipping().undoPositioned();
  653. effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
  654. }, arguments[1] || { })
  655. );
  656. };
  657. Effect.SlideUp = function(element) {
  658. element = $(element).cleanWhitespace();
  659. var oldInnerBottom = element.down().getStyle('bottom');
  660. var elementDimensions = element.getDimensions();
  661. return new Effect.Scale(element, window.opera ? 0 : 1,
  662. Object.extend({ scaleContent: false,
  663. scaleX: false,
  664. scaleMode: 'box',
  665. scaleFrom: 100,
  666. scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
  667. restoreAfterFinish: true,
  668. afterSetup: function(effect) {
  669. effect.element.makePositioned();
  670. effect.element.down().makePositioned();
  671. if (window.opera) effect.element.setStyle({top: ''});
  672. effect.element.makeClipping().show();
  673. },
  674. afterUpdateInternal: function(effect) {
  675. effect.element.down().setStyle({bottom:
  676. (effect.dims[0] - effect.element.clientHeight) + 'px' });
  677. },
  678. afterFinishInternal: function(effect) {
  679. effect.element.hide().undoClipping().undoPositioned();
  680. effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom});
  681. }
  682. }, arguments[1] || { })
  683. );
  684. };
  685. // Bug in opera makes the TD containing this element expand for a instance after finish
  686. Effect.Squish = function(element) {
  687. return new Effect.Scale(element, window.opera ? 1 : 0, {
  688. restoreAfterFinish: true,
  689. beforeSetup: function(effect) {
  690. effect.element.makeClipping();
  691. },
  692. afterFinishInternal: function(effect) {
  693. effect.element.hide().undoClipping();
  694. }
  695. });
  696. };
  697. Effect.Grow = function(element) {
  698. element = $(element);
  699. var options = Object.extend({
  700. direction: 'center',
  701. moveTransition: Effect.Transitions.sinoidal,
  702. scaleTransition: Effect.Transitions.sinoidal,
  703. opacityTransition: Effect.Transitions.full
  704. }, arguments[1] || { });
  705. var oldStyle = {
  706. top: element.style.top,
  707. left: element.style.left,
  708. height: element.style.height,
  709. width: element.style.width,
  710. opacity: element.getInlineOpacity() };
  711. var dims = element.getDimensions();
  712. var initialMoveX, initialMoveY;
  713. var moveX, moveY;
  714. switch (options.direction) {
  715. case 'top-left':
  716. initialMoveX = initialMoveY = moveX = moveY = 0;
  717. break;
  718. case 'top-right':
  719. initialMoveX = dims.width;
  720. initialMoveY = moveY = 0;
  721. moveX = -dims.width;
  722. break;
  723. case 'bottom-left':
  724. initialMoveX = moveX = 0;
  725. initialMoveY = dims.height;
  726. moveY = -dims.height;
  727. break;
  728. case 'bottom-right':
  729. initialMoveX = dims.width;
  730. initialMoveY = dims.height;
  731. moveX = -dims.width;
  732. moveY = -dims.height;
  733. break;
  734. case 'center':
  735. initialMoveX = dims.width / 2;
  736. initialMoveY = dims.height / 2;
  737. moveX = -dims.width / 2;
  738. moveY = -dims.height / 2;
  739. break;
  740. }
  741. return new Effect.Move(element, {
  742. x: initialMoveX,
  743. y: initialMoveY,
  744. duration: 0.01,
  745. beforeSetup: function(effect) {
  746. effect.element.hide().makeClipping().makePositioned();
  747. },
  748. afterFinishInternal: function(effect) {
  749. new Effect.Parallel(
  750. [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
  751. new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
  752. new Effect.Scale(effect.element, 100, {
  753. scaleMode: { originalHeight: dims.height, originalWidth: dims.width },
  754. sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
  755. ], Object.extend({
  756. beforeSetup: function(effect) {
  757. effect.effects[0].element.setStyle({height: '0px'}).show();
  758. },
  759. afterFinishInternal: function(effect) {
  760. effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle);
  761. }
  762. }, options)
  763. )
  764. }
  765. });
  766. };
  767. Effect.Shrink = 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.none
  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. var dims = element.getDimensions();
  782. var moveX, moveY;
  783. switch (options.direction) {
  784. case 'top-left':
  785. moveX = moveY = 0;
  786. break;
  787. case 'top-right':
  788. moveX = dims.width;
  789. moveY = 0;
  790. break;
  791. case 'bottom-left':
  792. moveX = 0;
  793. moveY = dims.height;
  794. break;
  795. case 'bottom-right':
  796. moveX = dims.width;
  797. moveY = dims.height;
  798. break;
  799. case 'center':
  800. moveX = dims.width / 2;
  801. moveY = dims.height / 2;
  802. break;
  803. }
  804. return new Effect.Parallel(
  805. [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
  806. new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
  807. new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
  808. ], Object.extend({
  809. beforeStartInternal: function(effect) {
  810. effect.effects[0].element.makePositioned().makeClipping();
  811. },
  812. afterFinishInternal: function(effect) {
  813. effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
  814. }, options)
  815. );
  816. };
  817. Effect.Pulsate = function(element) {
  818. element = $(element);
  819. var options = arguments[1] || { };
  820. var oldOpacity = element.getInlineOpacity();
  821. var transition = options.transition || Effect.Transitions.sinoidal;
  822. var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) };
  823. reverser.bind(transition);
  824. return new Effect.Opacity(element,
  825. Object.extend(Object.extend({ duration: 2.0, from: 0,
  826. afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
  827. }, options), {transition: reverser}));
  828. };
  829. Effect.Fold = function(element) {
  830. element = $(element);
  831. var oldStyle = {
  832. top: element.style.top,
  833. left: element.style.left,
  834. width: element.style.width,
  835. height: element.style.height };
  836. element.makeClipping();
  837. return new Effect.Scale(element, 5, Object.extend({
  838. scaleContent: false,
  839. scaleX: false,
  840. afterFinishInternal: function(effect) {
  841. new Effect.Scale(element, 1, {
  842. scaleContent: false,
  843. scaleY: false,
  844. afterFinishInternal: function(effect) {
  845. effect.element.hide().undoClipping().setStyle(oldStyle);
  846. } });
  847. }}, arguments[1] || { }));
  848. };
  849. Effect.Morph = Class.create(Effect.Base, {
  850. initialize: function(element) {
  851. this.element = $(element);
  852. if (!this.element) throw(Effect._elementDoesNotExistError);
  853. var options = Object.extend({
  854. style: { }
  855. }, arguments[1] || { });
  856. if (!Object.isString(options.style)) this.style = $H(options.style);
  857. else {
  858. if (options.style.include(':'))
  859. this.style = options.style.parseStyle();
  860. else {
  861. this.element.addClassName(options.style);
  862. this.style = $H(this.element.getStyles());
  863. this.element.removeClassName(options.style);
  864. var css = this.element.getStyles();
  865. this.style = this.style.reject(function(style) {
  866. return style.value == css[style.key];
  867. });
  868. options.afterFinishInternal = function(effect) {
  869. effect.element.addClassName(effect.options.style);
  870. effect.transforms.each(function(transform) {
  871. effect.element.style[transform.style] = '';
  872. });
  873. }
  874. }
  875. }
  876. this.start(options);
  877. },
  878. setup: function(){
  879. function parseColor(color){
  880. if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
  881. color = color.parseColor();
  882. return $R(0,2).map(function(i){
  883. return parseInt( color.slice(i*2+1,i*2+3), 16 )
  884. });
  885. }
  886. this.transforms = this.style.map(function(pair){
  887. var property = pair[0], value = pair[1], unit = null;
  888. if (value.parseColor('#zzzzzz') != '#zzzzzz') {
  889. value = value.parseColor();
  890. unit = 'color';
  891. } else if (property == 'opacity') {
  892. value = parseFloat(value);
  893. if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
  894. this.element.setStyle({zoom: 1});
  895. } else if (Element.CSS_LENGTH.test(value)) {
  896. var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
  897. value = parseFloat(components[1]);
  898. unit = (components.length == 3) ? components[2] : null;
  899. }
  900. var originalValue = this.element.getStyle(property);
  901. return {
  902. style: property.camelize(),
  903. originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0),
  904. targetValue: unit=='color' ? parseColor(value) : value,
  905. unit: unit
  906. };
  907. }.bind(this)).reject(function(transform){
  908. return (
  909. (transform.originalValue == transform.targetValue) ||
  910. (
  911. transform.unit != 'color' &&
  912. (isNaN(transform.originalValue) || isNaN(transform.targetValue))
  913. )
  914. )
  915. });
  916. },
  917. update: function(position) {
  918. var style = { }, transform, i = this.transforms.length;
  919. while(i--)
  920. style[(transform = this.transforms[i]).style] =
  921. transform.unit=='color' ? '#'+
  922. (Math.round(transform.originalValue[0]+
  923. (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +
  924. (Math.round(transform.originalValue[1]+
  925. (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() +
  926. (Math.round(transform.originalValue[2]+
  927. (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :
  928. (transform.originalValue +
  929. (transform.targetValue - transform.originalValue) * position).toFixed(3) +
  930. (transform.unit === null ? '' : transform.unit);
  931. this.element.setStyle(style, true);
  932. }
  933. });
  934. Effect.Transform = Class.create({
  935. initialize: function(tracks){
  936. this.tracks = [];
  937. this.options = arguments[1] || { };
  938. this.addTracks(tracks);
  939. },
  940. addTracks: function(tracks){
  941. tracks.each(function(track){
  942. track = $H(track);
  943. var data = track.values().first();
  944. this.tracks.push($H({
  945. ids: track.keys().first(),
  946. effect: Effect.Morph,
  947. options: { style: data }
  948. }));
  949. }.bind(this));
  950. return this;
  951. },
  952. play: function(){
  953. return new Effect.Parallel(
  954. this.tracks.map(function(track){
  955. var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options');
  956. var elements = [$(ids) || $$(ids)].flatten();
  957. return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) });
  958. }).flatten(),
  959. this.options
  960. );
  961. }
  962. });
  963. Element.CSS_PROPERTIES = $w(
  964. 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' +
  965. 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
  966. 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
  967. 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
  968. 'fontSize fontWeight height left letterSpacing lineHeight ' +
  969. 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
  970. 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
  971. 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
  972. 'right textIndent top width wordSpacing zIndex');
  973. Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
  974. String.__parseStyleElement = document.createElement('div');
  975. String.prototype.parseStyle = function(){
  976. var style, styleRules = $H();
  977. if (Prototype.Browser.WebKit)
  978. style = new Element('div',{style:this}).style;
  979. else {
  980. String.__parseStyleElement.innerHTML = '<div style="' + this + '"></div>';
  981. style = String.__parseStyleElement.childNodes[0].style;
  982. }
  983. Element.CSS_PROPERTIES.each(function(property){
  984. if (style[property]) styleRules.set(property, style[property]);
  985. });
  986. if (Prototype.Browser.IE && this.include('opacity'))
  987. styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]);
  988. return styleRules;
  989. };
  990. if (document.defaultView && document.defaultView.getComputedStyle) {
  991. Element.getStyles = function(element) {
  992. var css = document.defaultView.getComputedStyle($(element), null);
  993. return Element.CSS_PROPERTIES.inject({ }, function(styles, property) {
  994. styles[property] = css[property];
  995. return styles;
  996. });
  997. };
  998. } else {
  999. Element.getStyles = function(element) {
  1000. element = $(element);
  1001. var css = element.currentStyle, styles;
  1002. styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) {
  1003. results[property] = css[property];
  1004. return results;
  1005. });
  1006. if (!styles.opacity) styles.opacity = element.getOpacity();
  1007. return styles;
  1008. };
  1009. };
  1010. Effect.Methods = {
  1011. morph: function(element, style) {
  1012. element = $(element);
  1013. new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { }));
  1014. return element;
  1015. },
  1016. visualEffect: function(element, effect, options) {
  1017. element = $(element)
  1018. var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1);
  1019. new Effect[klass](element, options);
  1020. return element;
  1021. },
  1022. highlight: function(element, options) {
  1023. element = $(element);
  1024. new Effect.Highlight(element, options);
  1025. return element;
  1026. }
  1027. };
  1028. $w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+
  1029. 'pulsate shake puff squish switchOff dropOut').each(
  1030. function(effect) {
  1031. Effect.Methods[effect] = function(element, options){
  1032. element = $(element);
  1033. Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options);
  1034. return element;
  1035. }
  1036. }
  1037. );
  1038. $w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each(
  1039. function(f) { Effect.Methods[f] = Element[f]; }
  1040. );
  1041. Element.addMethods(Effect.Methods);