PageRenderTime 30ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 1ms

/examples/rails/public/javascripts/effects.js

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