effects.js 38 KB

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