PageRenderTime 39ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 1ms

/include/js/scriptaculous.php

https://bitbucket.org/icarito/pmc
PHP | 3560 lines | 3064 code | 297 blank | 199 comment | 549 complexity | aa314ae85ddeef1f944fd9cea6e9c166 MD5 | raw file
Possible License(s): GPL-3.0, LGPL-2.1
  1. <?php
  2. ob_start("ob_gzhandler");
  3. header("Content-type: text/javascript; charset=utf-8");
  4. header("Cache-Control: must-revalidate");
  5. $offset = 600 * 60 ;
  6. $ExpStr = "Expires:" .
  7. gmdate("D, d M Y H:i:s",time() + $offset) . "GMT";
  8. header($ExpStr);
  9. ?>
  10. // script.aculo.us scriptaculous.js v1.8.2, Tue Nov 18 18:30:58 +0100 2008
  11. // Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
  12. //
  13. // Permission is hereby granted, free of charge, to any person obtaining
  14. // a copy of this software and associated documentation files (the
  15. // "Software"), to deal in the Software without restriction, including
  16. // without limitation the rights to use, copy, modify, merge, publish,
  17. // distribute, sublicense, and/or sell copies of the Software, and to
  18. // permit persons to whom the Software is furnished to do so, subject to
  19. // the following conditions:
  20. //
  21. // The above copyright notice and this permission notice shall be
  22. // included in all copies or substantial portions of the Software.
  23. //
  24. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  25. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  26. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  27. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  28. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  29. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  30. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  31. //
  32. // For details, see the script.aculo.us web site: http://script.aculo.us/
  33. var Scriptaculous = {
  34. Version: '1.8.2',
  35. require: function(libraryName) {
  36. // inserting via DOM fails in Safari 2.0, so brute force approach
  37. document.write('<script type="text/javascript" src="'+libraryName+'"><\/script>');
  38. },
  39. REQUIRED_PROTOTYPE: '1.6.0.3',
  40. load: function() {
  41. function convertVersionString(versionString) {
  42. var v = versionString.replace(/_.*|\./g, '');
  43. v = parseInt(v + '0'.times(4-v.length));
  44. return versionString.indexOf('_') > -1 ? v-1 : v;
  45. }
  46. if((typeof Prototype=='undefined') ||
  47. (typeof Element == 'undefined') ||
  48. (typeof Element.Methods=='undefined') ||
  49. (convertVersionString(Prototype.Version) <
  50. convertVersionString(Scriptaculous.REQUIRED_PROTOTYPE)))
  51. throw("script.aculo.us requires the Prototype JavaScript framework >= " +
  52. Scriptaculous.REQUIRED_PROTOTYPE);
  53. /*
  54. var js = /scriptaculous\.js(\?.*)?$/;
  55. $$('head script[src]').findAll(function(s) {
  56. return s.src.match(js);
  57. }).each(function(s) {
  58. var path = s.src.replace(js, ''),
  59. includes = s.src.match(/\?.*load=([a-z,]*)/);
  60. (includes ? includes[1] : 'builder,effects,dragdrop,controls,slider,sound').split(',').each(
  61. function(include) { Scriptaculous.require(path+include+'.js') });
  62. });
  63. }*/
  64. }
  65. }
  66. // script.aculo.us effects.js v1.8.2, Tue Nov 18 18:30:58 +0100 2008
  67. // Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
  68. // Contributors:
  69. // Justin Palmer (http://encytemedia.com/)
  70. // Mark Pilgrim (http://diveintomark.org/)
  71. // Martin Bialasinki
  72. //
  73. // script.aculo.us is freely distributable under the terms of an MIT-style license.
  74. // For details, see the script.aculo.us web site: http://script.aculo.us/
  75. // converts rgb() and #xxx to #xxxxxx format,
  76. // returns self (or first argument) if not convertable
  77. String.prototype.parseColor = function() {
  78. var color = '#';
  79. if (this.slice(0,4) == 'rgb(') {
  80. var cols = this.slice(4,this.length-1).split(',');
  81. var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
  82. } else {
  83. if (this.slice(0,1) == '#') {
  84. if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();
  85. if (this.length==7) color = this.toLowerCase();
  86. }
  87. }
  88. return (color.length==7 ? color : (arguments[0] || this));
  89. };
  90. /*--------------------------------------------------------------------------*/
  91. Element.collectTextNodes = function(element) {
  92. return $A($(element).childNodes).collect( function(node) {
  93. return (node.nodeType==3 ? node.nodeValue :
  94. (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
  95. }).flatten().join('');
  96. };
  97. Element.collectTextNodesIgnoreClass = function(element, className) {
  98. return $A($(element).childNodes).collect( function(node) {
  99. return (node.nodeType==3 ? node.nodeValue :
  100. ((node.hasChildNodes() && !Element.hasClassName(node,className)) ?
  101. Element.collectTextNodesIgnoreClass(node, className) : ''));
  102. }).flatten().join('');
  103. };
  104. Element.setContentZoom = function(element, percent) {
  105. element = $(element);
  106. element.setStyle({fontSize: (percent/100) + 'em'});
  107. if (Prototype.Browser.WebKit) window.scrollBy(0,0);
  108. return element;
  109. };
  110. Element.getInlineOpacity = function(element){
  111. return $(element).style.opacity || '';
  112. };
  113. Element.forceRerendering = function(element) {
  114. try {
  115. element = $(element);
  116. var n = document.createTextNode(' ');
  117. element.appendChild(n);
  118. element.removeChild(n);
  119. } catch(e) { }
  120. };
  121. /*--------------------------------------------------------------------------*/
  122. var Effect = {
  123. _elementDoesNotExistError: {
  124. name: 'ElementDoesNotExistError',
  125. message: 'The specified DOM element does not exist, but is required for this effect to operate'
  126. },
  127. Transitions: {
  128. linear: Prototype.K,
  129. sinoidal: function(pos) {
  130. return (-Math.cos(pos*Math.PI)/2) + .5;
  131. },
  132. reverse: function(pos) {
  133. return 1-pos;
  134. },
  135. flicker: function(pos) {
  136. var pos = ((-Math.cos(pos*Math.PI)/4) + .75) + Math.random()/4;
  137. return pos > 1 ? 1 : pos;
  138. },
  139. wobble: function(pos) {
  140. return (-Math.cos(pos*Math.PI*(9*pos))/2) + .5;
  141. },
  142. pulse: function(pos, pulses) {
  143. return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5;
  144. },
  145. spring: function(pos) {
  146. return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6));
  147. },
  148. none: function(pos) {
  149. return 0;
  150. },
  151. full: function(pos) {
  152. return 1;
  153. }
  154. },
  155. DefaultOptions: {
  156. duration: 1.0, // seconds
  157. fps: 100, // 100= assume 66fps max.
  158. sync: false, // true for combining
  159. from: 0.0,
  160. to: 1.0,
  161. delay: 0.0,
  162. queue: 'parallel'
  163. },
  164. tagifyText: function(element) {
  165. var tagifyStyle = 'position:relative';
  166. if (Prototype.Browser.IE) tagifyStyle += ';zoom:1';
  167. element = $(element);
  168. $A(element.childNodes).each( function(child) {
  169. if (child.nodeType==3) {
  170. child.nodeValue.toArray().each( function(character) {
  171. element.insertBefore(
  172. new Element('span', {style: tagifyStyle}).update(
  173. character == ' ' ? String.fromCharCode(160) : character),
  174. child);
  175. });
  176. Element.remove(child);
  177. }
  178. });
  179. },
  180. multiple: function(element, effect) {
  181. var elements;
  182. if (((typeof element == 'object') ||
  183. Object.isFunction(element)) &&
  184. (element.length))
  185. elements = element;
  186. else
  187. elements = $(element).childNodes;
  188. var options = Object.extend({
  189. speed: 0.1,
  190. delay: 0.0
  191. }, arguments[2] || { });
  192. var masterDelay = options.delay;
  193. $A(elements).each( function(element, index) {
  194. new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
  195. });
  196. },
  197. PAIRS: {
  198. 'slide': ['SlideDown','SlideUp'],
  199. 'blind': ['BlindDown','BlindUp'],
  200. 'appear': ['Appear','Fade']
  201. },
  202. toggle: function(element, effect) {
  203. element = $(element);
  204. effect = (effect || 'appear').toLowerCase();
  205. var options = Object.extend({
  206. queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
  207. }, arguments[2] || { });
  208. Effect[element.visible() ?
  209. Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
  210. }
  211. };
  212. Effect.DefaultOptions.transition = Effect.Transitions.sinoidal;
  213. /* ------------- core effects ------------- */
  214. Effect.ScopedQueue = Class.create(Enumerable, {
  215. initialize: function() {
  216. this.effects = [];
  217. this.interval = null;
  218. },
  219. _each: function(iterator) {
  220. this.effects._each(iterator);
  221. },
  222. add: function(effect) {
  223. var timestamp = new Date().getTime();
  224. var position = Object.isString(effect.options.queue) ?
  225. effect.options.queue : effect.options.queue.position;
  226. switch(position) {
  227. case 'front':
  228. // move unstarted effects after this effect
  229. this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
  230. e.startOn += effect.finishOn;
  231. e.finishOn += effect.finishOn;
  232. });
  233. break;
  234. case 'with-last':
  235. timestamp = this.effects.pluck('startOn').max() || timestamp;
  236. break;
  237. case 'end':
  238. // start effect after last queued effect has finished
  239. timestamp = this.effects.pluck('finishOn').max() || timestamp;
  240. break;
  241. }
  242. effect.startOn += timestamp;
  243. effect.finishOn += timestamp;
  244. if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
  245. this.effects.push(effect);
  246. if (!this.interval)
  247. this.interval = setInterval(this.loop.bind(this), 15);
  248. },
  249. remove: function(effect) {
  250. this.effects = this.effects.reject(function(e) { return e==effect });
  251. if (this.effects.length == 0) {
  252. clearInterval(this.interval);
  253. this.interval = null;
  254. }
  255. },
  256. loop: function() {
  257. var timePos = new Date().getTime();
  258. for(var i=0, len=this.effects.length;i<len;i++)
  259. this.effects[i] && this.effects[i].loop(timePos);
  260. }
  261. });
  262. Effect.Queues = {
  263. instances: $H(),
  264. get: function(queueName) {
  265. if (!Object.isString(queueName)) return queueName;
  266. return this.instances.get(queueName) ||
  267. this.instances.set(queueName, new Effect.ScopedQueue());
  268. }
  269. };
  270. Effect.Queue = Effect.Queues.get('global');
  271. Effect.Base = Class.create({
  272. position: null,
  273. start: function(options) {
  274. function codeForEvent(options,eventName){
  275. return (
  276. (options[eventName+'Internal'] ? 'this.options.'+eventName+'Internal(this);' : '') +
  277. (options[eventName] ? 'this.options.'+eventName+'(this);' : '')
  278. );
  279. }
  280. if (options && options.transition === false) options.transition = Effect.Transitions.linear;
  281. this.options = Object.extend(Object.extend({ },Effect.DefaultOptions), options || { });
  282. this.currentFrame = 0;
  283. this.state = 'idle';
  284. this.startOn = this.options.delay*1000;
  285. this.finishOn = this.startOn+(this.options.duration*1000);
  286. this.fromToDelta = this.options.to-this.options.from;
  287. this.totalTime = this.finishOn-this.startOn;
  288. this.totalFrames = this.options.fps*this.options.duration;
  289. this.render = (function() {
  290. function dispatch(effect, eventName) {
  291. if (effect.options[eventName + 'Internal'])
  292. effect.options[eventName + 'Internal'](effect);
  293. if (effect.options[eventName])
  294. effect.options[eventName](effect);
  295. }
  296. return function(pos) {
  297. if (this.state === "idle") {
  298. this.state = "running";
  299. dispatch(this, 'beforeSetup');
  300. if (this.setup) this.setup();
  301. dispatch(this, 'afterSetup');
  302. }
  303. if (this.state === "running") {
  304. pos = (this.options.transition(pos) * this.fromToDelta) + this.options.from;
  305. this.position = pos;
  306. dispatch(this, 'beforeUpdate');
  307. if (this.update) this.update(pos);
  308. dispatch(this, 'afterUpdate');
  309. }
  310. };
  311. })();
  312. this.event('beforeStart');
  313. if (!this.options.sync)
  314. Effect.Queues.get(Object.isString(this.options.queue) ?
  315. 'global' : this.options.queue.scope).add(this);
  316. },
  317. loop: function(timePos) {
  318. if (timePos >= this.startOn) {
  319. if (timePos >= this.finishOn) {
  320. this.render(1.0);
  321. this.cancel();
  322. this.event('beforeFinish');
  323. if (this.finish) this.finish();
  324. this.event('afterFinish');
  325. return;
  326. }
  327. var pos = (timePos - this.startOn) / this.totalTime,
  328. frame = (pos * this.totalFrames).round();
  329. if (frame > this.currentFrame) {
  330. this.render(pos);
  331. this.currentFrame = frame;
  332. }
  333. }
  334. },
  335. cancel: function() {
  336. if (!this.options.sync)
  337. Effect.Queues.get(Object.isString(this.options.queue) ?
  338. 'global' : this.options.queue.scope).remove(this);
  339. this.state = 'finished';
  340. },
  341. event: function(eventName) {
  342. if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
  343. if (this.options[eventName]) this.options[eventName](this);
  344. },
  345. inspect: function() {
  346. var data = $H();
  347. for(property in this)
  348. if (!Object.isFunction(this[property])) data.set(property, this[property]);
  349. return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';
  350. }
  351. });
  352. Effect.Parallel = Class.create(Effect.Base, {
  353. initialize: function(effects) {
  354. this.effects = effects || [];
  355. this.start(arguments[1]);
  356. },
  357. update: function(position) {
  358. this.effects.invoke('render', position);
  359. },
  360. finish: function(position) {
  361. this.effects.each( function(effect) {
  362. effect.render(1.0);
  363. effect.cancel();
  364. effect.event('beforeFinish');
  365. if (effect.finish) effect.finish(position);
  366. effect.event('afterFinish');
  367. });
  368. }
  369. });
  370. Effect.Tween = Class.create(Effect.Base, {
  371. initialize: function(object, from, to) {
  372. object = Object.isString(object) ? $(object) : object;
  373. var args = $A(arguments), method = args.last(),
  374. options = args.length == 5 ? args[3] : null;
  375. this.method = Object.isFunction(method) ? method.bind(object) :
  376. Object.isFunction(object[method]) ? object[method].bind(object) :
  377. function(value) { object[method] = value };
  378. this.start(Object.extend({ from: from, to: to }, options || { }));
  379. },
  380. update: function(position) {
  381. this.method(position);
  382. }
  383. });
  384. Effect.Event = Class.create(Effect.Base, {
  385. initialize: function() {
  386. this.start(Object.extend({ duration: 0 }, arguments[0] || { }));
  387. },
  388. update: Prototype.emptyFunction
  389. });
  390. Effect.Opacity = Class.create(Effect.Base, {
  391. initialize: function(element) {
  392. this.element = $(element);
  393. if (!this.element) throw(Effect._elementDoesNotExistError);
  394. // make this work on IE on elements without 'layout'
  395. if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
  396. this.element.setStyle({zoom: 1});
  397. var options = Object.extend({
  398. from: this.element.getOpacity() || 0.0,
  399. to: 1.0
  400. }, arguments[1] || { });
  401. this.start(options);
  402. },
  403. update: function(position) {
  404. this.element.setOpacity(position);
  405. }
  406. });
  407. Effect.Move = Class.create(Effect.Base, {
  408. initialize: function(element) {
  409. this.element = $(element);
  410. if (!this.element) throw(Effect._elementDoesNotExistError);
  411. var options = Object.extend({
  412. x: 0,
  413. y: 0,
  414. mode: 'relative'
  415. }, arguments[1] || { });
  416. this.start(options);
  417. },
  418. setup: function() {
  419. this.element.makePositioned();
  420. this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
  421. this.originalTop = parseFloat(this.element.getStyle('top') || '0');
  422. if (this.options.mode == 'absolute') {
  423. this.options.x = this.options.x - this.originalLeft;
  424. this.options.y = this.options.y - this.originalTop;
  425. }
  426. },
  427. update: function(position) {
  428. this.element.setStyle({
  429. left: (this.options.x * position + this.originalLeft).round() + 'px',
  430. top: (this.options.y * position + this.originalTop).round() + 'px'
  431. });
  432. }
  433. });
  434. // for backwards compatibility
  435. Effect.MoveBy = function(element, toTop, toLeft) {
  436. return new Effect.Move(element,
  437. Object.extend({ x: toLeft, y: toTop }, arguments[3] || { }));
  438. };
  439. Effect.Scale = Class.create(Effect.Base, {
  440. initialize: function(element, percent) {
  441. this.element = $(element);
  442. if (!this.element) throw(Effect._elementDoesNotExistError);
  443. var options = Object.extend({
  444. scaleX: true,
  445. scaleY: true,
  446. scaleContent: true,
  447. scaleFromCenter: false,
  448. scaleMode: 'box', // 'box' or 'contents' or { } with provided values
  449. scaleFrom: 100.0,
  450. scaleTo: percent
  451. }, arguments[2] || { });
  452. this.start(options);
  453. },
  454. setup: function() {
  455. this.restoreAfterFinish = this.options.restoreAfterFinish || false;
  456. this.elementPositioning = this.element.getStyle('position');
  457. this.originalStyle = { };
  458. ['top','left','width','height','fontSize'].each( function(k) {
  459. this.originalStyle[k] = this.element.style[k];
  460. }.bind(this));
  461. this.originalTop = this.element.offsetTop;
  462. this.originalLeft = this.element.offsetLeft;
  463. var fontSize = this.element.getStyle('font-size') || '100%';
  464. ['em','px','%','pt'].each( function(fontSizeType) {
  465. if (fontSize.indexOf(fontSizeType)>0) {
  466. this.fontSize = parseFloat(fontSize);
  467. this.fontSizeType = fontSizeType;
  468. }
  469. }.bind(this));
  470. this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
  471. this.dims = null;
  472. if (this.options.scaleMode=='box')
  473. this.dims = [this.element.offsetHeight, this.element.offsetWidth];
  474. if (/^content/.test(this.options.scaleMode))
  475. this.dims = [this.element.scrollHeight, this.element.scrollWidth];
  476. if (!this.dims)
  477. this.dims = [this.options.scaleMode.originalHeight,
  478. this.options.scaleMode.originalWidth];
  479. },
  480. update: function(position) {
  481. var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
  482. if (this.options.scaleContent && this.fontSize)
  483. this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
  484. this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
  485. },
  486. finish: function(position) {
  487. if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
  488. },
  489. setDimensions: function(height, width) {
  490. var d = { };
  491. if (this.options.scaleX) d.width = width.round() + 'px';
  492. if (this.options.scaleY) d.height = height.round() + 'px';
  493. if (this.options.scaleFromCenter) {
  494. var topd = (height - this.dims[0])/2;
  495. var leftd = (width - this.dims[1])/2;
  496. if (this.elementPositioning == 'absolute') {
  497. if (this.options.scaleY) d.top = this.originalTop-topd + 'px';
  498. if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
  499. } else {
  500. if (this.options.scaleY) d.top = -topd + 'px';
  501. if (this.options.scaleX) d.left = -leftd + 'px';
  502. }
  503. }
  504. this.element.setStyle(d);
  505. }
  506. });
  507. Effect.Highlight = Class.create(Effect.Base, {
  508. initialize: function(element) {
  509. this.element = $(element);
  510. if (!this.element) throw(Effect._elementDoesNotExistError);
  511. var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { });
  512. this.start(options);
  513. },
  514. setup: function() {
  515. // Prevent executing on elements not in the layout flow
  516. if (this.element.getStyle('display')=='none') { this.cancel(); return; }
  517. // Disable background image during the effect
  518. this.oldStyle = { };
  519. if (!this.options.keepBackgroundImage) {
  520. this.oldStyle.backgroundImage = this.element.getStyle('background-image');
  521. this.element.setStyle({backgroundImage: 'none'});
  522. }
  523. if (!this.options.endcolor)
  524. this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
  525. if (!this.options.restorecolor)
  526. this.options.restorecolor = this.element.getStyle('background-color');
  527. // init color calculations
  528. this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
  529. 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));
  530. },
  531. update: function(position) {
  532. this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
  533. return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) });
  534. },
  535. finish: function() {
  536. this.element.setStyle(Object.extend(this.oldStyle, {
  537. backgroundColor: this.options.restorecolor
  538. }));
  539. }
  540. });
  541. Effect.ScrollTo = function(element) {
  542. var options = arguments[1] || { },
  543. scrollOffsets = document.viewport.getScrollOffsets(),
  544. elementOffsets = $(element).cumulativeOffset();
  545. if (options.offset) elementOffsets[1] += options.offset;
  546. return new Effect.Tween(null,
  547. scrollOffsets.top,
  548. elementOffsets[1],
  549. options,
  550. function(p){ scrollTo(scrollOffsets.left, p.round()); }
  551. );
  552. };
  553. /* ------------- combination effects ------------- */
  554. Effect.Fade = function(element) {
  555. element = $(element);
  556. var oldOpacity = element.getInlineOpacity();
  557. var options = Object.extend({
  558. from: element.getOpacity() || 1.0,
  559. to: 0.0,
  560. afterFinishInternal: function(effect) {
  561. if (effect.options.to!=0) return;
  562. effect.element.hide().setStyle({opacity: oldOpacity});
  563. }
  564. }, arguments[1] || { });
  565. return new Effect.Opacity(element,options);
  566. };
  567. Effect.Appear = function(element) {
  568. element = $(element);
  569. var options = Object.extend({
  570. from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
  571. to: 1.0,
  572. // force Safari to render floated elements properly
  573. afterFinishInternal: function(effect) {
  574. effect.element.forceRerendering();
  575. },
  576. beforeSetup: function(effect) {
  577. effect.element.setOpacity(effect.options.from).show();
  578. }}, arguments[1] || { });
  579. return new Effect.Opacity(element,options);
  580. };
  581. Effect.Puff = function(element) {
  582. element = $(element);
  583. var oldStyle = {
  584. opacity: element.getInlineOpacity(),
  585. position: element.getStyle('position'),
  586. top: element.style.top,
  587. left: element.style.left,
  588. width: element.style.width,
  589. height: element.style.height
  590. };
  591. return new Effect.Parallel(
  592. [ new Effect.Scale(element, 200,
  593. { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
  594. new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
  595. Object.extend({ duration: 1.0,
  596. beforeSetupInternal: function(effect) {
  597. Position.absolutize(effect.effects[0].element);
  598. },
  599. afterFinishInternal: function(effect) {
  600. effect.effects[0].element.hide().setStyle(oldStyle); }
  601. }, arguments[1] || { })
  602. );
  603. };
  604. Effect.BlindUp = function(element) {
  605. element = $(element);
  606. element.makeClipping();
  607. return new Effect.Scale(element, 0,
  608. Object.extend({ scaleContent: false,
  609. scaleX: false,
  610. restoreAfterFinish: true,
  611. afterFinishInternal: function(effect) {
  612. effect.element.hide().undoClipping();
  613. }
  614. }, arguments[1] || { })
  615. );
  616. };
  617. Effect.BlindDown = function(element) {
  618. element = $(element);
  619. var elementDimensions = element.getDimensions();
  620. return new Effect.Scale(element, 100, Object.extend({
  621. scaleContent: false,
  622. scaleX: false,
  623. scaleFrom: 0,
  624. scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
  625. restoreAfterFinish: true,
  626. afterSetup: function(effect) {
  627. effect.element.makeClipping().setStyle({height: '0px'}).show();
  628. },
  629. afterFinishInternal: function(effect) {
  630. effect.element.undoClipping();
  631. }
  632. }, arguments[1] || { }));
  633. };
  634. Effect.SwitchOff = function(element) {
  635. element = $(element);
  636. var oldOpacity = element.getInlineOpacity();
  637. return new Effect.Appear(element, Object.extend({
  638. duration: 0.4,
  639. from: 0,
  640. transition: Effect.Transitions.flicker,
  641. afterFinishInternal: function(effect) {
  642. new Effect.Scale(effect.element, 1, {
  643. duration: 0.3, scaleFromCenter: true,
  644. scaleX: false, scaleContent: false, restoreAfterFinish: true,
  645. beforeSetup: function(effect) {
  646. effect.element.makePositioned().makeClipping();
  647. },
  648. afterFinishInternal: function(effect) {
  649. effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
  650. }
  651. });
  652. }
  653. }, arguments[1] || { }));
  654. };
  655. Effect.DropOut = function(element) {
  656. element = $(element);
  657. var oldStyle = {
  658. top: element.getStyle('top'),
  659. left: element.getStyle('left'),
  660. opacity: element.getInlineOpacity() };
  661. return new Effect.Parallel(
  662. [ new Effect.Move(element, {x: 0, y: 100, sync: true }),
  663. new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
  664. Object.extend(
  665. { duration: 0.5,
  666. beforeSetup: function(effect) {
  667. effect.effects[0].element.makePositioned();
  668. },
  669. afterFinishInternal: function(effect) {
  670. effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
  671. }
  672. }, arguments[1] || { }));
  673. };
  674. Effect.Shake = function(element) {
  675. element = $(element);
  676. var options = Object.extend({
  677. distance: 20,
  678. duration: 0.5
  679. }, arguments[1] || {});
  680. var distance = parseFloat(options.distance);
  681. var split = parseFloat(options.duration) / 10.0;
  682. var oldStyle = {
  683. top: element.getStyle('top'),
  684. left: element.getStyle('left') };
  685. return new Effect.Move(element,
  686. { x: distance, y: 0, duration: split, afterFinishInternal: function(effect) {
  687. new Effect.Move(effect.element,
  688. { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
  689. new Effect.Move(effect.element,
  690. { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
  691. new Effect.Move(effect.element,
  692. { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
  693. new Effect.Move(effect.element,
  694. { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
  695. new Effect.Move(effect.element,
  696. { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) {
  697. effect.element.undoPositioned().setStyle(oldStyle);
  698. }}); }}); }}); }}); }}); }});
  699. };
  700. Effect.SlideDown = function(element) {
  701. element = $(element).cleanWhitespace();
  702. // SlideDown need to have the content of the element wrapped in a container element with fixed height!
  703. var oldInnerBottom = element.down().getStyle('bottom');
  704. var elementDimensions = element.getDimensions();
  705. return new Effect.Scale(element, 100, Object.extend({
  706. scaleContent: false,
  707. scaleX: false,
  708. scaleFrom: window.opera ? 0 : 1,
  709. scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
  710. restoreAfterFinish: true,
  711. afterSetup: function(effect) {
  712. effect.element.makePositioned();
  713. effect.element.down().makePositioned();
  714. if (window.opera) effect.element.setStyle({top: ''});
  715. effect.element.makeClipping().setStyle({height: '0px'}).show();
  716. },
  717. afterUpdateInternal: function(effect) {
  718. effect.element.down().setStyle({bottom:
  719. (effect.dims[0] - effect.element.clientHeight) + 'px' });
  720. },
  721. afterFinishInternal: function(effect) {
  722. effect.element.undoClipping().undoPositioned();
  723. effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
  724. }, arguments[1] || { })
  725. );
  726. };
  727. Effect.SlideUp = function(element) {
  728. element = $(element).cleanWhitespace();
  729. var oldInnerBottom = element.down().getStyle('bottom');
  730. var elementDimensions = element.getDimensions();
  731. return new Effect.Scale(element, window.opera ? 0 : 1,
  732. Object.extend({ scaleContent: false,
  733. scaleX: false,
  734. scaleMode: 'box',
  735. scaleFrom: 100,
  736. scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
  737. restoreAfterFinish: true,
  738. afterSetup: function(effect) {
  739. effect.element.makePositioned();
  740. effect.element.down().makePositioned();
  741. if (window.opera) effect.element.setStyle({top: ''});
  742. effect.element.makeClipping().show();
  743. },
  744. afterUpdateInternal: function(effect) {
  745. effect.element.down().setStyle({bottom:
  746. (effect.dims[0] - effect.element.clientHeight) + 'px' });
  747. },
  748. afterFinishInternal: function(effect) {
  749. effect.element.hide().undoClipping().undoPositioned();
  750. effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom});
  751. }
  752. }, arguments[1] || { })
  753. );
  754. };
  755. // Bug in opera makes the TD containing this element expand for a instance after finish
  756. Effect.Squish = function(element) {
  757. return new Effect.Scale(element, window.opera ? 1 : 0, {
  758. restoreAfterFinish: true,
  759. beforeSetup: function(effect) {
  760. effect.element.makeClipping();
  761. },
  762. afterFinishInternal: function(effect) {
  763. effect.element.hide().undoClipping();
  764. }
  765. });
  766. };
  767. Effect.Grow = function(element) {
  768. element = $(element);
  769. var options = Object.extend({
  770. direction: 'center',
  771. moveTransition: Effect.Transitions.sinoidal,
  772. scaleTransition: Effect.Transitions.sinoidal,
  773. opacityTransition: Effect.Transitions.full
  774. }, arguments[1] || { });
  775. var oldStyle = {
  776. top: element.style.top,
  777. left: element.style.left,
  778. height: element.style.height,
  779. width: element.style.width,
  780. opacity: element.getInlineOpacity() };
  781. var dims = element.getDimensions();
  782. var initialMoveX, initialMoveY;
  783. var moveX, moveY;
  784. switch (options.direction) {
  785. case 'top-left':
  786. initialMoveX = initialMoveY = moveX = moveY = 0;
  787. break;
  788. case 'top-right':
  789. initialMoveX = dims.width;
  790. initialMoveY = moveY = 0;
  791. moveX = -dims.width;
  792. break;
  793. case 'bottom-left':
  794. initialMoveX = moveX = 0;
  795. initialMoveY = dims.height;
  796. moveY = -dims.height;
  797. break;
  798. case 'bottom-right':
  799. initialMoveX = dims.width;
  800. initialMoveY = dims.height;
  801. moveX = -dims.width;
  802. moveY = -dims.height;
  803. break;
  804. case 'center':
  805. initialMoveX = dims.width / 2;
  806. initialMoveY = dims.height / 2;
  807. moveX = -dims.width / 2;
  808. moveY = -dims.height / 2;
  809. break;
  810. }
  811. return new Effect.Move(element, {
  812. x: initialMoveX,
  813. y: initialMoveY,
  814. duration: 0.01,
  815. beforeSetup: function(effect) {
  816. effect.element.hide().makeClipping().makePositioned();
  817. },
  818. afterFinishInternal: function(effect) {
  819. new Effect.Parallel(
  820. [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
  821. new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
  822. new Effect.Scale(effect.element, 100, {
  823. scaleMode: { originalHeight: dims.height, originalWidth: dims.width },
  824. sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
  825. ], Object.extend({
  826. beforeSetup: function(effect) {
  827. effect.effects[0].element.setStyle({height: '0px'}).show();
  828. },
  829. afterFinishInternal: function(effect) {
  830. effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle);
  831. }
  832. }, options)
  833. );
  834. }
  835. });
  836. };
  837. Effect.Shrink = function(element) {
  838. element = $(element);
  839. var options = Object.extend({
  840. direction: 'center',
  841. moveTransition: Effect.Transitions.sinoidal,
  842. scaleTransition: Effect.Transitions.sinoidal,
  843. opacityTransition: Effect.Transitions.none
  844. }, arguments[1] || { });
  845. var oldStyle = {
  846. top: element.style.top,
  847. left: element.style.left,
  848. height: element.style.height,
  849. width: element.style.width,
  850. opacity: element.getInlineOpacity() };
  851. var dims = element.getDimensions();
  852. var moveX, moveY;
  853. switch (options.direction) {
  854. case 'top-left':
  855. moveX = moveY = 0;
  856. break;
  857. case 'top-right':
  858. moveX = dims.width;
  859. moveY = 0;
  860. break;
  861. case 'bottom-left':
  862. moveX = 0;
  863. moveY = dims.height;
  864. break;
  865. case 'bottom-right':
  866. moveX = dims.width;
  867. moveY = dims.height;
  868. break;
  869. case 'center':
  870. moveX = dims.width / 2;
  871. moveY = dims.height / 2;
  872. break;
  873. }
  874. return new Effect.Parallel(
  875. [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
  876. new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
  877. new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
  878. ], Object.extend({
  879. beforeStartInternal: function(effect) {
  880. effect.effects[0].element.makePositioned().makeClipping();
  881. },
  882. afterFinishInternal: function(effect) {
  883. effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
  884. }, options)
  885. );
  886. };
  887. Effect.Pulsate = function(element) {
  888. element = $(element);
  889. var options = arguments[1] || { },
  890. oldOpacity = element.getInlineOpacity(),
  891. transition = options.transition || Effect.Transitions.linear,
  892. reverser = function(pos){
  893. return 1 - transition((-Math.cos((pos*(options.pulses||5)*2)*Math.PI)/2) + .5);
  894. };
  895. return new Effect.Opacity(element,
  896. Object.extend(Object.extend({ duration: 2.0, from: 0,
  897. afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
  898. }, options), {transition: reverser}));
  899. };
  900. Effect.Fold = function(element) {
  901. element = $(element);
  902. var oldStyle = {
  903. top: element.style.top,
  904. left: element.style.left,
  905. width: element.style.width,
  906. height: element.style.height };
  907. element.makeClipping();
  908. return new Effect.Scale(element, 5, Object.extend({
  909. scaleContent: false,
  910. scaleX: false,
  911. afterFinishInternal: function(effect) {
  912. new Effect.Scale(element, 1, {
  913. scaleContent: false,
  914. scaleY: false,
  915. afterFinishInternal: function(effect) {
  916. effect.element.hide().undoClipping().setStyle(oldStyle);
  917. } });
  918. }}, arguments[1] || { }));
  919. };
  920. Effect.Morph = Class.create(Effect.Base, {
  921. initialize: function(element) {
  922. this.element = $(element);
  923. if (!this.element) throw(Effect._elementDoesNotExistError);
  924. var options = Object.extend({
  925. style: { }
  926. }, arguments[1] || { });
  927. if (!Object.isString(options.style)) this.style = $H(options.style);
  928. else {
  929. if (options.style.include(':'))
  930. this.style = options.style.parseStyle();
  931. else {
  932. this.element.addClassName(options.style);
  933. this.style = $H(this.element.getStyles());
  934. this.element.removeClassName(options.style);
  935. var css = this.element.getStyles();
  936. this.style = this.style.reject(function(style) {
  937. return style.value == css[style.key];
  938. });
  939. options.afterFinishInternal = function(effect) {
  940. effect.element.addClassName(effect.options.style);
  941. effect.transforms.each(function(transform) {
  942. effect.element.style[transform.style] = '';
  943. });
  944. };
  945. }
  946. }
  947. this.start(options);
  948. },
  949. setup: function(){
  950. function parseColor(color){
  951. if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
  952. color = color.parseColor();
  953. return $R(0,2).map(function(i){
  954. return parseInt( color.slice(i*2+1,i*2+3), 16 );
  955. });
  956. }
  957. this.transforms = this.style.map(function(pair){
  958. var property = pair[0], value = pair[1], unit = null;
  959. if (value.parseColor('#zzzzzz') != '#zzzzzz') {
  960. value = value.parseColor();
  961. unit = 'color';
  962. } else if (property == 'opacity') {
  963. value = parseFloat(value);
  964. if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
  965. this.element.setStyle({zoom: 1});
  966. } else if (Element.CSS_LENGTH.test(value)) {
  967. var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
  968. value = parseFloat(components[1]);
  969. unit = (components.length == 3) ? components[2] : null;
  970. }
  971. var originalValue = this.element.getStyle(property);
  972. return {
  973. style: property.camelize(),
  974. originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0),
  975. targetValue: unit=='color' ? parseColor(value) : value,
  976. unit: unit
  977. };
  978. }.bind(this)).reject(function(transform){
  979. return (
  980. (transform.originalValue == transform.targetValue) ||
  981. (
  982. transform.unit != 'color' &&
  983. (isNaN(transform.originalValue) || isNaN(transform.targetValue))
  984. )
  985. );
  986. });
  987. },
  988. update: function(position) {
  989. var style = { }, transform, i = this.transforms.length;
  990. while(i--)
  991. style[(transform = this.transforms[i]).style] =
  992. transform.unit=='color' ? '#'+
  993. (Math.round(transform.originalValue[0]+
  994. (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +
  995. (Math.round(transform.originalValue[1]+
  996. (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() +
  997. (Math.round(transform.originalValue[2]+
  998. (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :
  999. (transform.originalValue +
  1000. (transform.targetValue - transform.originalValue) * position).toFixed(3) +
  1001. (transform.unit === null ? '' : transform.unit);
  1002. this.element.setStyle(style, true);
  1003. }
  1004. });
  1005. Effect.Transform = Class.create({
  1006. initialize: function(tracks){
  1007. this.tracks = [];
  1008. this.options = arguments[1] || { };
  1009. this.addTracks(tracks);
  1010. },
  1011. addTracks: function(tracks){
  1012. tracks.each(function(track){
  1013. track = $H(track);
  1014. var data = track.values().first();
  1015. this.tracks.push($H({
  1016. ids: track.keys().first(),
  1017. effect: Effect.Morph,
  1018. options: { style: data }
  1019. }));
  1020. }.bind(this));
  1021. return this;
  1022. },
  1023. play: function(){
  1024. return new Effect.Parallel(
  1025. this.tracks.map(function(track){
  1026. var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options');
  1027. var elements = [$(ids) || $$(ids)].flatten();
  1028. return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) });
  1029. }).flatten(),
  1030. this.options
  1031. );
  1032. }
  1033. });
  1034. Element.CSS_PROPERTIES = $w(
  1035. 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' +
  1036. 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
  1037. 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
  1038. 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
  1039. 'fontSize fontWeight height left letterSpacing lineHeight ' +
  1040. 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
  1041. 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
  1042. 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
  1043. 'right textIndent top width wordSpacing zIndex');
  1044. Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
  1045. String.__parseStyleElement = document.createElement('div');
  1046. String.prototype.parseStyle = function(){
  1047. var style, styleRules = $H();
  1048. if (Prototype.Browser.WebKit)
  1049. style = new Element('div',{style:this}).style;
  1050. else {
  1051. String.__parseStyleElement.innerHTML = '<div style="' + this + '"></div>';
  1052. style = String.__parseStyleElement.childNodes[0].style;
  1053. }
  1054. Element.CSS_PROPERTIES.each(function(property){
  1055. if (style[property]) styleRules.set(property, style[property]);
  1056. });
  1057. if (Prototype.Browser.IE && this.include('opacity'))
  1058. styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]);
  1059. return styleRules;
  1060. };
  1061. if (document.defaultView && document.defaultView.getComputedStyle) {
  1062. Element.getStyles = function(element) {
  1063. var css = document.defaultView.getComputedStyle($(element), null);
  1064. return Element.CSS_PROPERTIES.inject({ }, function(styles, property) {
  1065. styles[property] = css[property];
  1066. return styles;
  1067. });
  1068. };
  1069. } else {
  1070. Element.getStyles = function(element) {
  1071. element = $(element);
  1072. var css = element.currentStyle, styles;
  1073. styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) {
  1074. results[property] = css[property];
  1075. return results;
  1076. });
  1077. if (!styles.opacity) styles.opacity = element.getOpacity();
  1078. return styles;
  1079. };
  1080. }
  1081. Effect.Methods = {
  1082. morph: function(element, style) {
  1083. element = $(element);
  1084. new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { }));
  1085. return element;
  1086. },
  1087. visualEffect: function(element, effect, options) {
  1088. element = $(element);
  1089. var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1);
  1090. new Effect[klass](element, options);
  1091. return element;
  1092. },
  1093. highlight: function(element, options) {
  1094. element = $(element);
  1095. new Effect.Highlight(element, options);
  1096. return element;
  1097. }
  1098. };
  1099. $w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+
  1100. 'pulsate shake puff squish switchOff dropOut').each(
  1101. function(effect) {
  1102. Effect.Methods[effect] = function(element, options){
  1103. element = $(element);
  1104. Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options);
  1105. return element;
  1106. };
  1107. }
  1108. );
  1109. $w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each(
  1110. function(f) { Effect.Methods[f] = Element[f]; }
  1111. );
  1112. Element.addMethods(Effect.Methods);
  1113. // script.aculo.us dragdrop.js v1.8.2, Tue Nov 18 18:30:58 +0100 2008
  1114. // Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
  1115. // (c) 2005-2008 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
  1116. //
  1117. // script.aculo.us is freely distributable under the terms of an MIT-style license.
  1118. // For details, see the script.aculo.us web site: http://script.aculo.us/
  1119. if(Object.isUndefined(Effect))
  1120. throw("dragdrop.js requires including script.aculo.us' effects.js library");
  1121. var Droppables = {
  1122. drops: [],
  1123. remove: function(element) {
  1124. this.drops = this.drops.reject(function(d) { return d.element==$(element) });
  1125. },
  1126. add: function(element) {
  1127. element = $(element);
  1128. var options = Object.extend({
  1129. greedy: true,
  1130. hoverclass: null,
  1131. tree: false
  1132. }, arguments[1] || { });
  1133. // cache containers
  1134. if(options.containment) {
  1135. options._containers = [];
  1136. var containment = options.containment;
  1137. if(Object.isArray(containment)) {
  1138. containment.each( function(c) { options._containers.push($(c)) });
  1139. } else {
  1140. options._containers.push($(containment));
  1141. }
  1142. }
  1143. if(options.accept) options.accept = [options.accept].flatten();
  1144. Element.makePositioned(element); // fix IE
  1145. options.element = element;
  1146. this.drops.push(options);
  1147. },
  1148. findDeepestChild: function(drops) {
  1149. deepest = drops[0];
  1150. for (i = 1; i < drops.length; ++i)
  1151. if (Element.isParent(drops[i].element, deepest.element))
  1152. deepest = drops[i];
  1153. return deepest;
  1154. },
  1155. isContained: function(element, drop) {
  1156. var containmentNode;
  1157. if(drop.tree) {
  1158. containmentNode = element.treeNode;
  1159. } else {
  1160. containmentNode = element.parentNode;
  1161. }
  1162. return drop._containers.detect(function(c) { return containmentNode == c });
  1163. },
  1164. isAffected: function(point, element, drop) {
  1165. return (
  1166. (drop.element!=element) &&
  1167. ((!drop._containers) ||
  1168. this.isContained(element, drop)) &&
  1169. ((!drop.accept) ||
  1170. (Element.classNames(element).detect(
  1171. function(v) { return drop.accept.include(v) } ) )) &&
  1172. Position.within(drop.element, point[0], point[1]) );
  1173. },
  1174. deactivate: function(drop) {
  1175. if(drop.hoverclass)
  1176. Element.removeClassName(drop.element, drop.hoverclass);
  1177. this.last_active = null;
  1178. },
  1179. activate: function(drop) {
  1180. if(drop.hoverclass)
  1181. Element.addClassName(drop.element, drop.hoverclass);
  1182. this.last_active = drop;
  1183. },
  1184. show: function(point, element) {
  1185. if(!this.drops.length) return;
  1186. var drop, affected = [];
  1187. this.drops.each( function(drop) {
  1188. if(Droppables.isAffected(point, element, drop))
  1189. affected.push(drop);
  1190. });
  1191. if(affected.length>0)
  1192. drop = Droppables.findDeepestChild(affected);
  1193. if(this.last_active && this.last_active != drop) this.deactivate(this.last_active);
  1194. if (drop) {
  1195. Position.within(drop.element, point[0], point[1]);
  1196. if(drop.onHover)
  1197. drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
  1198. if (drop != this.last_active) Droppables.activate(drop);
  1199. }
  1200. },
  1201. fire: function(event, element) {
  1202. if(!this.last_active) return;
  1203. Position.prepare();
  1204. if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
  1205. if (this.last_active.onDrop) {
  1206. this.last_active.onDrop(element, this.last_active.element, event);
  1207. return true;
  1208. }
  1209. },
  1210. reset: function() {
  1211. if(this.last_active)
  1212. this.deactivate(this.last_active);
  1213. }
  1214. };
  1215. var Draggables = {
  1216. drags: [],
  1217. observers: [],
  1218. register: function(draggable) {
  1219. if(this.drags.length == 0) {
  1220. this.eventMouseUp = this.endDrag.bindAsEventListener(this);
  1221. this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
  1222. this.eventKeypress = this.keyPress.bindAsEventListener(this);
  1223. Event.observe(document, "mouseup", this.eventMouseUp);
  1224. Event.observe(document, "mousemove", this.eventMouseMove);
  1225. Event.observe(document, "keypress", this.eventKeypress);
  1226. }
  1227. this.drags.push(draggable);
  1228. },
  1229. unregister: function(draggable) {
  1230. this.drags = this.drags.reject(function(d) { return d==draggable });
  1231. if(this.drags.length == 0) {
  1232. Event.stopObserving(document, "mouseup", this.eventMouseUp);
  1233. Event.stopObserving(document, "mousemove", this.eventMouseMove);
  1234. Event.stopObserving(document, "keypress", this.eventKeypress);
  1235. }
  1236. },
  1237. activate: function(draggable) {
  1238. if(draggable.options.delay) {
  1239. this._timeout = setTimeout(function() {
  1240. Draggables._timeout = null;
  1241. window.focus();
  1242. Draggables.activeDraggable = draggable;
  1243. }.bind(this), draggable.options.delay);
  1244. } else {
  1245. window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
  1246. this.activeDraggable = draggable;
  1247. }
  1248. },
  1249. deactivate: function() {
  1250. this.activeDraggable = null;
  1251. },
  1252. updateDrag: function(event) {
  1253. if(!this.activeDraggable) return;
  1254. var pointer = [Event.pointerX(event), Event.pointerY(event)];
  1255. // Mozilla-based browsers fire successive mousemove events with
  1256. // the same coordinates, prevent needless redrawing (moz bug?)
  1257. if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
  1258. this._lastPointer = pointer;
  1259. this.activeDraggable.updateDrag(event, pointer);
  1260. },
  1261. endDrag: function(event) {
  1262. if(this._timeout) {
  1263. clearTimeout(this._timeout);
  1264. this._timeout = null;
  1265. }
  1266. if(!this.activeDraggable) return;
  1267. this._lastPointer = null;
  1268. this.activeDraggable.endDrag(event);
  1269. this.activeDraggable = null;
  1270. },
  1271. keyPress: function(event) {
  1272. if(this.activeDraggable)
  1273. this.activeDraggable.keyPress(event);
  1274. },
  1275. addObserver: function(observer) {
  1276. this.observers.push(observer);
  1277. this._cacheObserverCallbacks();
  1278. },
  1279. removeObserver: function(element) { // element instead of observer fixes mem leaks
  1280. this.observers = this.observers.reject( function(o) { return o.element==element });
  1281. this._cacheObserverCallbacks();
  1282. },
  1283. notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag'
  1284. if(this[eventName+'Count'] > 0)
  1285. this.observers.each( function(o) {
  1286. if(o[eventName]) o[eventName](eventName, draggable, event);
  1287. });
  1288. if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
  1289. },
  1290. _cacheObserverCallbacks: function() {
  1291. ['onStart','onEnd','onDrag'].each( function(eventName) {
  1292. Draggables[eventName+'Count'] = Draggables.observers.select(
  1293. function(o) { return o[eventName]; }
  1294. ).length;
  1295. });
  1296. }
  1297. };
  1298. /*--------------------------------------------------------------------------*/
  1299. var Draggable = Class.create({
  1300. initialize: function(element) {
  1301. var defaults = {
  1302. handle: false,
  1303. reverteffect: function(element, top_offset, left_offset) {
  1304. var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
  1305. new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
  1306. queue: {scope:'_draggable', position:'end'}
  1307. });
  1308. },
  1309. endeffect: function(element) {
  1310. var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0;
  1311. new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,
  1312. queue: {scope:'_draggable', position:'end'},
  1313. afterFinish: function(){
  1314. Draggable._dragging[element] = false
  1315. }
  1316. });
  1317. },
  1318. zindex: 1000,
  1319. revert: false,
  1320. quiet: false,
  1321. scroll: false,
  1322. scrollSensitivity: 20,
  1323. scrollSpeed: 15,
  1324. snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] }
  1325. delay: 0
  1326. };
  1327. if(!arguments[1] || Object.isUndefined(arguments[1].endeffect))
  1328. Object.extend(defaults, {
  1329. starteffect: function(element) {
  1330. element._opacity = Element.getOpacity(element);
  1331. Draggable._dragging[element] = true;
  1332. new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});
  1333. }
  1334. });
  1335. var options = Object.extend(defaults, arguments[1] || { });
  1336. this.element = $(element);
  1337. if(options.handle && Object.isString(options.handle))
  1338. this.handle = this.element.down('.'+options.handle, 0);
  1339. if(!this.handle) this.handle = $(options.handle);
  1340. if(!this.handle) this.handle = this.element;
  1341. if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
  1342. options.scroll = $(options.scroll);
  1343. this._isScrollChild = Element.childOf(this.element, options.scroll);
  1344. }
  1345. Element.makePositioned(this.element); // fix IE
  1346. this.options = options;
  1347. this.dragging = false;
  1348. this.eventMouseDown = this.initDrag.bindAsEventListener(this);
  1349. Event.observe(this.handle, "mousedown", this.eventMouseDown);
  1350. Draggables.register(this);
  1351. },
  1352. destroy: function() {
  1353. Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
  1354. Draggables.unregister(this);
  1355. },
  1356. currentDelta: function() {
  1357. return([
  1358. parseInt(Element.getStyle(this.element,'left') || '0'),
  1359. parseInt(Element.getStyle(this.element,'top') || '0')]);
  1360. },
  1361. initDrag: function(event) {
  1362. if(!Object.isUndefined(Draggable._dragging[this.element]) &&
  1363. Draggable._dragging[this.element]) return;
  1364. if(Event.isLeftClick(event)) {
  1365. // abort on form elements, fixes a Firefox issue
  1366. var src = Event.element(event);
  1367. if((tag_name = src.tagName.toUpperCase()) && (
  1368. tag_name=='INPUT' ||
  1369. tag_name=='SELECT' ||
  1370. tag_name=='OPTION' ||
  1371. tag_name=='BUTTON' ||
  1372. tag_name=='TEXTAREA')) return;
  1373. var pointer = [Event.pointerX(event), Event.pointerY(event)];
  1374. var pos = Position.cumulativeOffset(this.element);
  1375. this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
  1376. Draggables.activate(this);
  1377. Event.stop(event);
  1378. }
  1379. },
  1380. startDrag: function(event) {
  1381. this.dragging = true;
  1382. if(!this.delta)
  1383. this.delta = this.currentDelta();
  1384. if(this.options.zindex) {
  1385. this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
  1386. this.element.style.zIndex = this.options.zindex;
  1387. }
  1388. if(this.options.ghosting) {
  1389. this._clone = this.element.cloneNode(true);
  1390. this._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
  1391. if (!this._originallyAbsolute)
  1392. Position.absolutize(this.element);
  1393. this.element.parentNode.insertBefore(this._clone, this.element);
  1394. }
  1395. if(this.options.scroll) {
  1396. if (this.options.scroll == window) {
  1397. var where = this._getWindowScroll(this.options.scroll);
  1398. this.originalScrollLeft = where.left;
  1399. this.originalScrollTop = where.top;
  1400. } else {
  1401. this.originalScrollLeft = this.options.scroll.scrollLeft;
  1402. this.originalScrollTop = this.options.scroll.scrollTop;
  1403. }
  1404. }
  1405. Draggables.notify('onStart', this, event);
  1406. if(this.options.starteffect) this.options.starteffect(this.element);
  1407. },
  1408. updateDrag: function(event, pointer) {
  1409. if(!this.dragging) this.startDrag(event);
  1410. if(!this.options.quiet){
  1411. Position.prepare();
  1412. Droppables.show(pointer, this.element);
  1413. }
  1414. Draggables.notify('onDrag', this, event);
  1415. this.draw(pointer);
  1416. if(this.options.change) this.options.change(this);
  1417. if(this.options.scroll) {
  1418. this.stopScrolling();
  1419. var p;
  1420. if (this.options.scroll == window) {
  1421. with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
  1422. } else {
  1423. p = Position.page(this.options.scroll);
  1424. p[0] += this.options.scroll.scrollLeft + Position.deltaX;
  1425. p[1] += this.options.scroll.scrollTop + Position.deltaY;
  1426. p.push(p[0]+this.options.scroll.offsetWidth);
  1427. p.push(p[1]+this.options.scroll.offsetHeight);
  1428. }
  1429. var speed = [0,0];
  1430. if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
  1431. if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
  1432. if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
  1433. if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
  1434. this.startScrolling(speed);
  1435. }
  1436. // fix AppleWebKit rendering
  1437. if(Prototype.Browser.WebKit) window.scrollBy(0,0);
  1438. Event.stop(event);
  1439. },
  1440. finishDrag: function(event, success) {
  1441. this.dragging = false;
  1442. if(this.options.quiet){
  1443. Position.prepare();
  1444. var pointer = [Event.pointerX(event), Event.pointerY(event)];
  1445. Droppables.show(pointer, this.element);
  1446. }
  1447. if(this.options.ghosting) {
  1448. if (!this._originallyAbsolute)
  1449. Position.relativize(this.element);
  1450. delete this._originallyAbsolute;
  1451. Element.remove(this._clone);
  1452. this._clone = null;
  1453. }
  1454. var dropped = false;
  1455. if(success) {
  1456. dropped = Droppables.fire(event, this.element);
  1457. if (!dropped) dropped = false;
  1458. }
  1459. if(dropped && this.options.onDropped) this.options.onDropped(this.element);
  1460. Draggables.notify('onEnd', this, event);
  1461. var revert = this.options.revert;
  1462. if(revert && Object.isFunction(revert)) revert = revert(this.element);
  1463. var d = this.currentDelta();
  1464. if(revert && this.options.reverteffect) {
  1465. if (dropped == 0 || revert != 'failure')
  1466. this.options.reverteffect(this.element,
  1467. d[1]-this.delta[1], d[0]-this.delta[0]);
  1468. } else {
  1469. this.delta = d;
  1470. }
  1471. if(this.options.zindex)
  1472. this.element.style.zIndex = this.originalZ;
  1473. if(this.options.endeffect)
  1474. this.options.endeffect(this.element);
  1475. Draggables.deactivate(this);
  1476. Droppables.reset();
  1477. },
  1478. keyPress: function(event) {
  1479. if(event.keyCode!=Event.KEY_ESC) return;
  1480. this.finishDrag(event, false);
  1481. Event.stop(event);
  1482. },
  1483. endDrag: function(event) {
  1484. if(!this.dragging) return;
  1485. this.stopScrolling();
  1486. this.finishDrag(event, true);
  1487. Event.stop(event);
  1488. },
  1489. draw: function(point) {
  1490. var pos = Position.cumulativeOffset(this.element);
  1491. if(this.options.ghosting) {
  1492. var r = Position.realOffset(this.element);
  1493. pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
  1494. }
  1495. var d = this.currentDelta();
  1496. pos[0] -= d[0]; pos[1] -= d[1];
  1497. if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
  1498. pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
  1499. pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
  1500. }
  1501. var p = [0,1].map(function(i){
  1502. return (point[i]-pos[i]-this.offset[i])
  1503. }.bind(this));
  1504. if(this.options.snap) {
  1505. if(Object.isFunction(this.options.snap)) {
  1506. p = this.options.snap(p[0],p[1],this);
  1507. } else {
  1508. if(Object.isArray(this.options.snap)) {
  1509. p = p.map( function(v, i) {
  1510. return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this));
  1511. } else {
  1512. p = p.map( function(v) {
  1513. return (v/this.options.snap).round()*this.options.snap }.bind(this));
  1514. }
  1515. }}
  1516. var style = this.element.style;
  1517. if((!this.options.constraint) || (this.options.constraint=='horizontal'))
  1518. style.left = p[0] + "px";
  1519. if((!this.options.constraint) || (this.options.constraint=='vertical'))
  1520. style.top = p[1] + "px";
  1521. if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
  1522. },
  1523. stopScrolling: function() {
  1524. if(this.scrollInterval) {
  1525. clearInterval(this.scrollInterval);
  1526. this.scrollInterval = null;
  1527. Draggables._lastScrollPointer = null;
  1528. }
  1529. },
  1530. startScrolling: function(speed) {
  1531. if(!(speed[0] || speed[1])) return;
  1532. this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
  1533. this.lastScrolled = new Date();
  1534. this.scrollInterval = setInterval(this.scroll.bind(this), 10);
  1535. },
  1536. scroll: function() {
  1537. var current = new Date();
  1538. var delta = current - this.lastScrolled;
  1539. this.lastScrolled = current;
  1540. if(this.options.scroll == window) {
  1541. with (this._getWindowScroll(this.options.scroll)) {
  1542. if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
  1543. var d = delta / 1000;
  1544. this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
  1545. }
  1546. }
  1547. } else {
  1548. this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
  1549. this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000;
  1550. }
  1551. Position.prepare();
  1552. Droppables.show(Draggables._lastPointer, this.element);
  1553. Draggables.notify('onDrag', this);
  1554. if (this._isScrollChild) {
  1555. Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
  1556. Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
  1557. Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
  1558. if (Draggables._lastScrollPointer[0] < 0)
  1559. Draggables._lastScrollPointer[0] = 0;
  1560. if (Draggables._lastScrollPointer[1] < 0)
  1561. Draggables._lastScrollPointer[1] = 0;
  1562. this.draw(Draggables._lastScrollPointer);
  1563. }
  1564. if(this.options.change) this.options.change(this);
  1565. },
  1566. _getWindowScroll: function(w) {
  1567. var T, L, W, H;
  1568. with (w.document) {
  1569. if (w.document.documentElement && documentElement.scrollTop) {
  1570. T = documentElement.scrollTop;
  1571. L = documentElement.scrollLeft;
  1572. } else if (w.document.body) {
  1573. T = body.scrollTop;
  1574. L = body.scrollLeft;
  1575. }
  1576. if (w.innerWidth) {
  1577. W = w.innerWidth;
  1578. H = w.innerHeight;
  1579. } else if (w.document.documentElement && documentElement.clientWidth) {
  1580. W = documentElement.clientWidth;
  1581. H = documentElement.clientHeight;
  1582. } else {
  1583. W = body.offsetWidth;
  1584. H = body.offsetHeight;
  1585. }
  1586. }
  1587. return { top: T, left: L, width: W, height: H };
  1588. }
  1589. });
  1590. Draggable._dragging = { };
  1591. /*--------------------------------------------------------------------------*/
  1592. var SortableObserver = Class.create({
  1593. initialize: function(element, observer) {
  1594. this.element = $(element);
  1595. this.observer = observer;
  1596. this.lastValue = Sortable.serialize(this.element);
  1597. },
  1598. onStart: function() {
  1599. this.lastValue = Sortable.serialize(this.element);
  1600. },
  1601. onEnd: function() {
  1602. Sortable.unmark();
  1603. if(this.lastValue != Sortable.serialize(this.element))
  1604. this.observer(this.element)
  1605. }
  1606. });
  1607. var Sortable = {
  1608. SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
  1609. sortables: { },
  1610. _findRootElement: function(element) {
  1611. while (element.tagName.toUpperCase() != "BODY") {
  1612. if(element.id && Sortable.sortables[element.id]) return element;
  1613. element = element.parentNode;
  1614. }
  1615. },
  1616. options: function(element) {
  1617. element = Sortable._findRootElement($(element));
  1618. if(!element) return;
  1619. return Sortable.sortables[element.id];
  1620. },
  1621. destroy: function(element){
  1622. element = $(element);
  1623. var s = Sortable.sortables[element.id];
  1624. if(s) {
  1625. Draggables.removeObserver(s.element);
  1626. s.droppables.each(function(d){ Droppables.remove(d) });
  1627. s.draggables.invoke('destroy');
  1628. delete Sortable.sortables[s.element.id];
  1629. }
  1630. },
  1631. create: function(element) {
  1632. element = $(element);
  1633. var options = Object.extend({
  1634. element: element,
  1635. tag: 'li', // assumes li children, override with tag: 'tagname'
  1636. dropOnEmpty: false,
  1637. tree: false,
  1638. treeTag: 'ul',
  1639. overlap: 'vertical', // one of 'vertical', 'horizontal'
  1640. constraint: 'vertical', // one of 'vertical', 'horizontal', false
  1641. containment: element, // also takes array of elements (or id's); or false
  1642. handle: false, // or a CSS class
  1643. only: false,
  1644. delay: 0,
  1645. hoverclass: null,
  1646. ghosting: false,
  1647. quiet: false,
  1648. scroll: false,
  1649. scrollSensitivity: 20,
  1650. scrollSpeed: 15,
  1651. format: this.SERIALIZE_RULE,
  1652. // these take arrays of elements or ids and can be
  1653. // used for better initialization performance
  1654. elements: false,
  1655. handles: false,
  1656. onChange: Prototype.emptyFunction,
  1657. onUpdate: Prototype.emptyFunction
  1658. }, arguments[1] || { });
  1659. // clear any old sortable with same element
  1660. this.destroy(element);
  1661. // build options for the draggables
  1662. var options_for_draggable = {
  1663. revert: true,
  1664. quiet: options.quiet,
  1665. scroll: options.scroll,
  1666. scrollSpeed: options.scrollSpeed,
  1667. scrollSensitivity: options.scrollSensitivity,
  1668. delay: options.delay,
  1669. ghosting: options.ghosting,
  1670. constraint: options.constraint,
  1671. handle: options.handle };
  1672. if(options.starteffect)
  1673. options_for_draggable.starteffect = options.starteffect;
  1674. if(options.reverteffect)
  1675. options_for_draggable.reverteffect = options.reverteffect;
  1676. else
  1677. if(options.ghosting) options_for_draggable.reverteffect = function(element) {
  1678. element.style.top = 0;
  1679. element.style.left = 0;
  1680. };
  1681. if(options.endeffect)
  1682. options_for_draggable.endeffect = options.endeffect;
  1683. if(options.zindex)
  1684. options_for_draggable.zindex = options.zindex;
  1685. // build options for the droppables
  1686. var options_for_droppable = {
  1687. overlap: options.overlap,
  1688. containment: options.containment,
  1689. tree: options.tree,
  1690. hoverclass: options.hoverclass,
  1691. onHover: Sortable.onHover
  1692. };
  1693. var options_for_tree = {
  1694. onHover: Sortable.onEmptyHover,
  1695. overlap: options.overlap,
  1696. containment: options.containment,
  1697. hoverclass: options.hoverclass
  1698. };
  1699. // fix for gecko engine
  1700. Element.cleanWhitespace(element);
  1701. options.draggables = [];
  1702. options.droppables = [];
  1703. // drop on empty handling
  1704. if(options.dropOnEmpty || options.tree) {
  1705. Droppables.add(element, options_for_tree);
  1706. options.droppables.push(element);
  1707. }
  1708. (options.elements || this.findElements(element, options) || []).each( function(e,i) {
  1709. var handle = options.handles ? $(options.handles[i]) :
  1710. (options.handle ? $(e).select('.' + options.handle)[0] : e);
  1711. options.draggables.push(
  1712. new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
  1713. Droppables.add(e, options_for_droppable);
  1714. if(options.tree) e.treeNode = element;
  1715. options.droppables.push(e);
  1716. });
  1717. if(options.tree) {
  1718. (Sortable.findTreeElements(element, options) || []).each( function(e) {
  1719. Droppables.add(e, options_for_tree);
  1720. e.treeNode = element;
  1721. options.droppables.push(e);
  1722. });
  1723. }
  1724. // keep reference
  1725. this.sortables[element.id] = options;
  1726. // for onupdate
  1727. Draggables.addObserver(new SortableObserver(element, options.onUpdate));
  1728. },
  1729. // return all suitable-for-sortable elements in a guaranteed order
  1730. findElements: function(element, options) {
  1731. return Element.findChildren(
  1732. element, options.only, options.tree ? true : false, options.tag);
  1733. },
  1734. findTreeElements: function(element, options) {
  1735. return Element.findChildren(
  1736. element, options.only, options.tree ? true : false, options.treeTag);
  1737. },
  1738. onHover: function(element, dropon, overlap) {
  1739. if(Element.isParent(dropon, element)) return;
  1740. if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
  1741. return;
  1742. } else if(overlap>0.5) {
  1743. Sortable.mark(dropon, 'before');
  1744. if(dropon.previousSibling != element) {
  1745. var oldParentNode = element.parentNode;
  1746. element.style.visibility = "hidden"; // fix gecko rendering
  1747. dropon.parentNode.insertBefore(element, dropon);
  1748. if(dropon.parentNode!=oldParentNode)
  1749. Sortable.options(oldParentNode).onChange(element);
  1750. Sortable.options(dropon.parentNode).onChange(element);
  1751. }
  1752. } else {
  1753. Sortable.mark(dropon, 'after');
  1754. var nextElement = dropon.nextSibling || null;
  1755. if(nextElement != element) {
  1756. var oldParentNode = element.parentNode;
  1757. element.style.visibility = "hidden"; // fix gecko rendering
  1758. dropon.parentNode.insertBefore(element, nextElement);
  1759. if(dropon.parentNode!=oldParentNode)
  1760. Sortable.options(oldParentNode).onChange(element);
  1761. Sortable.options(dropon.parentNode).onChange(element);
  1762. }
  1763. }
  1764. },
  1765. onEmptyHover: function(element, dropon, overlap) {
  1766. var oldParentNode = element.parentNode;
  1767. var droponOptions = Sortable.options(dropon);
  1768. if(!Element.isParent(dropon, element)) {
  1769. var index;
  1770. var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
  1771. var child = null;
  1772. if(children) {
  1773. var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
  1774. for (index = 0; index < children.length; index += 1) {
  1775. if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
  1776. offset -= Element.offsetSize (children[index], droponOptions.overlap);
  1777. } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
  1778. child = index + 1 < children.length ? children[index + 1] : null;
  1779. break;
  1780. } else {
  1781. child = children[index];
  1782. break;
  1783. }
  1784. }
  1785. }
  1786. dropon.insertBefore(element, child);
  1787. Sortable.options(oldParentNode).onChange(element);
  1788. droponOptions.onChange(element);
  1789. }
  1790. },
  1791. unmark: function() {
  1792. if(Sortable._marker) Sortable._marker.hide();
  1793. },
  1794. mark: function(dropon, position) {
  1795. // mark on ghosting only
  1796. var sortable = Sortable.options(dropon.parentNode);
  1797. if(sortable && !sortable.ghosting) return;
  1798. if(!Sortable._marker) {
  1799. Sortable._marker =
  1800. ($('dropmarker') || Element.extend(document.createElement('DIV'))).
  1801. hide().addClassName('dropmarker').setStyle({position:'absolute'});
  1802. document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
  1803. }
  1804. var offsets = Position.cumulativeOffset(dropon);
  1805. Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
  1806. if(position=='after')
  1807. if(sortable.overlap == 'horizontal')
  1808. Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
  1809. else
  1810. Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
  1811. Sortable._marker.show();
  1812. },
  1813. _tree: function(element, options, parent) {
  1814. var children = Sortable.findElements(element, options) || [];
  1815. for (var i = 0; i < children.length; ++i) {
  1816. var match = children[i].id.match(options.format);
  1817. if (!match) continue;
  1818. var child = {
  1819. id: encodeURIComponent(match ? match[1] : null),
  1820. element: element,
  1821. parent: parent,
  1822. children: [],
  1823. position: parent.children.length,
  1824. container: $(children[i]).down(options.treeTag)
  1825. };
  1826. /* Get the element containing the children and recurse over it */
  1827. if (child.container)
  1828. this._tree(child.container, options, child);
  1829. parent.children.push (child);
  1830. }
  1831. return parent;
  1832. },
  1833. tree: function(element) {
  1834. element = $(element);
  1835. var sortableOptions = this.options(element);
  1836. var options = Object.extend({
  1837. tag: sortableOptions.tag,
  1838. treeTag: sortableOptions.treeTag,
  1839. only: sortableOptions.only,
  1840. name: element.id,
  1841. format: sortableOptions.format
  1842. }, arguments[1] || { });
  1843. var root = {
  1844. id: null,
  1845. parent: null,
  1846. children: [],
  1847. container: element,
  1848. position: 0
  1849. };
  1850. return Sortable._tree(element, options, root);
  1851. },
  1852. /* Construct a [i] index for a particular node */
  1853. _constructIndex: function(node) {
  1854. var index = '';
  1855. do {
  1856. if (node.id) index = '[' + node.position + ']' + index;
  1857. } while ((node = node.parent) != null);
  1858. return index;
  1859. },
  1860. sequence: function(element) {
  1861. element = $(element);
  1862. var options = Object.extend(this.options(element), arguments[1] || { });
  1863. return $(this.findElements(element, options) || []).map( function(item) {
  1864. return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
  1865. });
  1866. },
  1867. setSequence: function(element, new_sequence) {
  1868. element = $(element);
  1869. var options = Object.extend(this.options(element), arguments[2] || { });
  1870. var nodeMap = { };
  1871. this.findElements(element, options).each( function(n) {
  1872. if (n.id.match(options.format))
  1873. nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
  1874. n.parentNode.removeChild(n);
  1875. });
  1876. new_sequence.each(function(ident) {
  1877. var n = nodeMap[ident];
  1878. if (n) {
  1879. n[1].appendChild(n[0]);
  1880. delete nodeMap[ident];
  1881. }
  1882. });
  1883. },
  1884. serialize: function(element) {
  1885. element = $(element);
  1886. var options = Object.extend(Sortable.options(element), arguments[1] || { });
  1887. var name = encodeURIComponent(
  1888. (arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
  1889. if (options.tree) {
  1890. return Sortable.tree(element, arguments[1]).children.map( function (item) {
  1891. return [name + Sortable._constructIndex(item) + "[id]=" +
  1892. encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
  1893. }).flatten().join('&');
  1894. } else {
  1895. return Sortable.sequence(element, arguments[1]).map( function(item) {
  1896. return name + "[]=" + encodeURIComponent(item);
  1897. }).join('&');
  1898. }
  1899. }
  1900. };
  1901. // Returns true if child is contained within element
  1902. Element.isParent = function(child, element) {
  1903. if (!child.parentNode || child == element) return false;
  1904. if (child.parentNode == element) return true;
  1905. return Element.isParent(child.parentNode, element);
  1906. };
  1907. Element.findChildren = function(element, only, recursive, tagName) {
  1908. if(!element.hasChildNodes()) return null;
  1909. tagName = tagName.toUpperCase();
  1910. if(only) only = [only].flatten();
  1911. var elements = [];
  1912. $A(element.childNodes).each( function(e) {
  1913. if(e.tagName && e.tagName.toUpperCase()==tagName &&
  1914. (!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
  1915. elements.push(e);
  1916. if(recursive) {
  1917. var grandchildren = Element.findChildren(e, only, recursive, tagName);
  1918. if(grandchildren) elements.push(grandchildren);
  1919. }
  1920. });
  1921. return (elements.length>0 ? elements.flatten() : []);
  1922. };
  1923. Element.offsetSize = function (element, type) {
  1924. return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
  1925. };
  1926. // script.aculo.us controls.js v1.8.2, Tue Nov 18 18:30:58 +0100 2008
  1927. // Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
  1928. // (c) 2005-2008 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
  1929. // (c) 2005-2008 Jon Tirsen (http://www.tirsen.com)
  1930. // Contributors:
  1931. // Richard Livsey
  1932. // Rahul Bhargava
  1933. // Rob Wills
  1934. //
  1935. // script.aculo.us is freely distributable under the terms of an MIT-style license.
  1936. // For details, see the script.aculo.us web site: http://script.aculo.us/
  1937. // Autocompleter.Base handles all the autocompletion functionality
  1938. // that's independent of the data source for autocompletion. This
  1939. // includes drawing the autocompletion menu, observing keyboard
  1940. // and mouse events, and similar.
  1941. //
  1942. // Specific autocompleters need to provide, at the very least,
  1943. // a getUpdatedChoices function that will be invoked every time
  1944. // the text inside the monitored textbox changes. This method
  1945. // should get the text for which to provide autocompletion by
  1946. // invoking this.getToken(), NOT by directly accessing
  1947. // this.element.value. This is to allow incremental tokenized
  1948. // autocompletion. Specific auto-completion logic (AJAX, etc)
  1949. // belongs in getUpdatedChoices.
  1950. //
  1951. // Tokenized incremental autocompletion is enabled automatically
  1952. // when an autocompleter is instantiated with the 'tokens' option
  1953. // in the options parameter, e.g.:
  1954. // new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
  1955. // will incrementally autocomplete with a comma as the token.
  1956. // Additionally, ',' in the above example can be replaced with
  1957. // a token array, e.g. { tokens: [',', '\n'] } which
  1958. // enables autocompletion on multiple tokens. This is most
  1959. // useful when one of the tokens is \n (a newline), as it
  1960. // allows smart autocompletion after linebreaks.
  1961. if(typeof Effect == 'undefined')
  1962. throw("controls.js requires including script.aculo.us' effects.js library");
  1963. var Autocompleter = { };
  1964. Autocompleter.Base = Class.create({
  1965. baseInitialize: function(element, update, options) {
  1966. element = $(element);
  1967. this.element = element;
  1968. this.update = $(update);
  1969. this.hasFocus = false;
  1970. this.changed = false;
  1971. this.active = false;
  1972. this.index = 0;
  1973. this.entryCount = 0;
  1974. this.oldElementValue = this.element.value;
  1975. if(this.setOptions)
  1976. this.setOptions(options);
  1977. else
  1978. this.options = options || { };
  1979. this.options.paramName = this.options.paramName || this.element.name;
  1980. this.options.tokens = this.options.tokens || [];
  1981. this.options.frequency = this.options.frequency || 0.4;
  1982. this.options.minChars = this.options.minChars || 1;
  1983. this.options.onShow = this.options.onShow ||
  1984. function(element, update){
  1985. if(!update.style.position || update.style.position=='absolute') {
  1986. update.style.position = 'absolute';
  1987. Position.clone(element, update, {
  1988. setHeight: false,
  1989. offsetTop: element.offsetHeight
  1990. });
  1991. }
  1992. Effect.Appear(update,{duration:0.15});
  1993. };
  1994. this.options.onHide = this.options.onHide ||
  1995. function(element, update){ new Effect.Fade(update,{duration:0.15}) };
  1996. if(typeof(this.options.tokens) == 'string')
  1997. this.options.tokens = new Array(this.options.tokens);
  1998. // Force carriage returns as token delimiters anyway
  1999. if (!this.options.tokens.include('\n'))
  2000. this.options.tokens.push('\n');
  2001. this.observer = null;
  2002. this.element.setAttribute('autocomplete','off');
  2003. Element.hide(this.update);
  2004. Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
  2005. Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));
  2006. },
  2007. show: function() {
  2008. if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
  2009. if(!this.iefix &&
  2010. (Prototype.Browser.IE) &&
  2011. (Element.getStyle(this.update, 'position')=='absolute')) {
  2012. new Insertion.After(this.update,
  2013. '<iframe id="' + this.update.id + '_iefix" '+
  2014. 'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
  2015. 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
  2016. this.iefix = $(this.update.id+'_iefix');
  2017. }
  2018. if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
  2019. },
  2020. fixIEOverlapping: function() {
  2021. Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
  2022. this.iefix.style.zIndex = 1;
  2023. this.update.style.zIndex = 2;
  2024. Element.show(this.iefix);
  2025. },
  2026. hide: function() {
  2027. this.stopIndicator();
  2028. if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
  2029. if(this.iefix) Element.hide(this.iefix);
  2030. },
  2031. startIndicator: function() {
  2032. if(this.options.indicator) Element.show(this.options.indicator);
  2033. },
  2034. stopIndicator: function() {
  2035. if(this.options.indicator) Element.hide(this.options.indicator);
  2036. },
  2037. onKeyPress: function(event) {
  2038. if(this.active)
  2039. switch(event.keyCode) {
  2040. case Event.KEY_TAB:
  2041. case Event.KEY_RETURN:
  2042. this.selectEntry();
  2043. Event.stop(event);
  2044. case Event.KEY_ESC:
  2045. this.hide();
  2046. this.active = false;
  2047. Event.stop(event);
  2048. return;
  2049. case Event.KEY_LEFT:
  2050. case Event.KEY_RIGHT:
  2051. return;
  2052. case Event.KEY_UP:
  2053. this.markPrevious();
  2054. this.render();
  2055. Event.stop(event);
  2056. return;
  2057. case Event.KEY_DOWN:
  2058. this.markNext();
  2059. this.render();
  2060. Event.stop(event);
  2061. return;
  2062. }
  2063. else
  2064. if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
  2065. (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;
  2066. this.changed = true;
  2067. this.hasFocus = true;
  2068. if(this.observer) clearTimeout(this.observer);
  2069. this.observer =
  2070. setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
  2071. },
  2072. activate: function() {
  2073. this.changed = false;
  2074. this.hasFocus = true;
  2075. this.getUpdatedChoices();
  2076. },
  2077. onHover: function(event) {
  2078. var element = Event.findElement(event, 'LI');
  2079. if(this.index != element.autocompleteIndex)
  2080. {
  2081. this.index = element.autocompleteIndex;
  2082. this.render();
  2083. }
  2084. Event.stop(event);
  2085. },
  2086. onClick: function(event) {
  2087. var element = Event.findElement(event, 'LI');
  2088. this.index = element.autocompleteIndex;
  2089. this.selectEntry();
  2090. this.hide();
  2091. },
  2092. onBlur: function(event) {
  2093. // needed to make click events working
  2094. setTimeout(this.hide.bind(this), 250);
  2095. this.hasFocus = false;
  2096. this.active = false;
  2097. },
  2098. render: function() {
  2099. if(this.entryCount > 0) {
  2100. for (var i = 0; i < this.entryCount; i++)
  2101. this.index==i ?
  2102. Element.addClassName(this.getEntry(i),"selected") :
  2103. Element.removeClassName(this.getEntry(i),"selected");
  2104. if(this.hasFocus) {
  2105. this.show();
  2106. this.active = true;
  2107. }
  2108. } else {
  2109. this.active = false;
  2110. this.hide();
  2111. }
  2112. },
  2113. markPrevious: function() {
  2114. if(this.index > 0) this.index--;
  2115. else this.index = this.entryCount-1;
  2116. this.getEntry(this.index).scrollIntoView(true);
  2117. },
  2118. markNext: function() {
  2119. if(this.index < this.entryCount-1) this.index++;
  2120. else this.index = 0;
  2121. this.getEntry(this.index).scrollIntoView(false);
  2122. },
  2123. getEntry: function(index) {
  2124. return this.update.firstChild.childNodes[index];
  2125. },
  2126. getCurrentEntry: function() {
  2127. return this.getEntry(this.index);
  2128. },
  2129. selectEntry: function() {
  2130. this.active = false;
  2131. this.updateElement(this.getCurrentEntry());
  2132. },
  2133. updateElement: function(selectedElement) {
  2134. if (this.options.updateElement) {
  2135. this.options.updateElement(selectedElement);
  2136. return;
  2137. }
  2138. var value = '';
  2139. if (this.options.select) {
  2140. var nodes = $(selectedElement).select('.' + this.options.select) || [];
  2141. if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
  2142. } else
  2143. value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
  2144. var bounds = this.getTokenBounds();
  2145. if (bounds[0] != -1) {
  2146. var newValue = this.element.value.substr(0, bounds[0]);
  2147. var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
  2148. if (whitespace)
  2149. newValue += whitespace[0];
  2150. this.element.value = newValue + value + this.element.value.substr(bounds[1]);
  2151. } else {
  2152. this.element.value = value;
  2153. }
  2154. this.oldElementValue = this.element.value;
  2155. this.element.focus();
  2156. if (this.options.afterUpdateElement)
  2157. this.options.afterUpdateElement(this.element, selectedElement);
  2158. },
  2159. updateChoices: function(choices) {
  2160. if(!this.changed && this.hasFocus) {
  2161. this.update.innerHTML = choices;
  2162. Element.cleanWhitespace(this.update);
  2163. Element.cleanWhitespace(this.update.down());
  2164. if(this.update.firstChild && this.update.down().childNodes) {
  2165. this.entryCount =
  2166. this.update.down().childNodes.length;
  2167. for (var i = 0; i < this.entryCount; i++) {
  2168. var entry = this.getEntry(i);
  2169. entry.autocompleteIndex = i;
  2170. this.addObservers(entry);
  2171. }
  2172. } else {
  2173. this.entryCount = 0;
  2174. }
  2175. this.stopIndicator();
  2176. this.index = 0;
  2177. if(this.entryCount==1 && this.options.autoSelect) {
  2178. this.selectEntry();
  2179. this.hide();
  2180. } else {
  2181. this.render();
  2182. }
  2183. }
  2184. },
  2185. addObservers: function(element) {
  2186. Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
  2187. Event.observe(element, "click", this.onClick.bindAsEventListener(this));
  2188. },
  2189. onObserverEvent: function() {
  2190. this.changed = false;
  2191. this.tokenBounds = null;
  2192. if(this.getToken().length>=this.options.minChars) {
  2193. this.getUpdatedChoices();
  2194. } else {
  2195. this.active = false;
  2196. this.hide();
  2197. }
  2198. this.oldElementValue = this.element.value;
  2199. },
  2200. getToken: function() {
  2201. var bounds = this.getTokenBounds();
  2202. return this.element.value.substring(bounds[0], bounds[1]).strip();
  2203. },
  2204. getTokenBounds: function() {
  2205. if (null != this.tokenBounds) return this.tokenBounds;
  2206. var value = this.element.value;
  2207. if (value.strip().empty()) return [-1, 0];
  2208. var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);
  2209. var offset = (diff == this.oldElementValue.length ? 1 : 0);
  2210. var prevTokenPos = -1, nextTokenPos = value.length;
  2211. var tp;
  2212. for (var index = 0, l = this.options.tokens.length; index < l; ++index) {
  2213. tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
  2214. if (tp > prevTokenPos) prevTokenPos = tp;
  2215. tp = value.indexOf(this.options.tokens[index], diff + offset);
  2216. if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;
  2217. }
  2218. return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);
  2219. }
  2220. });
  2221. Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {
  2222. var boundary = Math.min(newS.length, oldS.length);
  2223. for (var index = 0; index < boundary; ++index)
  2224. if (newS[index] != oldS[index])
  2225. return index;
  2226. return boundary;
  2227. };
  2228. Ajax.Autocompleter = Class.create(Autocompleter.Base, {
  2229. initialize: function(element, update, url, options) {
  2230. this.baseInitialize(element, update, options);
  2231. this.options.asynchronous = true;
  2232. this.options.onComplete = this.onComplete.bind(this);
  2233. this.options.defaultParams = this.options.parameters || null;
  2234. this.url = url;
  2235. },
  2236. getUpdatedChoices: function() {
  2237. this.startIndicator();
  2238. var entry = encodeURIComponent(this.options.paramName) + '=' +
  2239. encodeURIComponent(this.getToken());
  2240. this.options.parameters = this.options.callback ?
  2241. this.options.callback(this.element, entry) : entry;
  2242. if(this.options.defaultParams)
  2243. this.options.parameters += '&' + this.options.defaultParams;
  2244. new Ajax.Request(this.url, this.options);
  2245. },
  2246. onComplete: function(request) {
  2247. this.updateChoices(request.responseText);
  2248. }
  2249. });
  2250. // The local array autocompleter. Used when you'd prefer to
  2251. // inject an array of autocompletion options into the page, rather
  2252. // than sending out Ajax queries, which can be quite slow sometimes.
  2253. //
  2254. // The constructor takes four parameters. The first two are, as usual,
  2255. // the id of the monitored textbox, and id of the autocompletion menu.
  2256. // The third is the array you want to autocomplete from, and the fourth
  2257. // is the options block.
  2258. //
  2259. // Extra local autocompletion options:
  2260. // - choices - How many autocompletion choices to offer
  2261. //
  2262. // - partialSearch - If false, the autocompleter will match entered
  2263. // text only at the beginning of strings in the
  2264. // autocomplete array. Defaults to true, which will
  2265. // match text at the beginning of any *word* in the
  2266. // strings in the autocomplete array. If you want to
  2267. // search anywhere in the string, additionally set
  2268. // the option fullSearch to true (default: off).
  2269. //
  2270. // - fullSsearch - Search anywhere in autocomplete array strings.
  2271. //
  2272. // - partialChars - How many characters to enter before triggering
  2273. // a partial match (unlike minChars, which defines
  2274. // how many characters are required to do any match
  2275. // at all). Defaults to 2.
  2276. //
  2277. // - ignoreCase - Whether to ignore case when autocompleting.
  2278. // Defaults to true.
  2279. //
  2280. // It's possible to pass in a custom function as the 'selector'
  2281. // option, if you prefer to write your own autocompletion logic.
  2282. // In that case, the other options above will not apply unless
  2283. // you support them.
  2284. Autocompleter.Local = Class.create(Autocompleter.Base, {
  2285. initialize: function(element, update, array, options) {
  2286. this.baseInitialize(element, update, options);
  2287. this.options.array = array;
  2288. },
  2289. getUpdatedChoices: function() {
  2290. this.updateChoices(this.options.selector(this));
  2291. },
  2292. setOptions: function(options) {
  2293. this.options = Object.extend({
  2294. choices: 10,
  2295. partialSearch: true,
  2296. partialChars: 2,
  2297. ignoreCase: true,
  2298. fullSearch: false,
  2299. selector: function(instance) {
  2300. var ret = []; // Beginning matches
  2301. var partial = []; // Inside matches
  2302. var entry = instance.getToken();
  2303. var count = 0;
  2304. for (var i = 0; i < instance.options.array.length &&
  2305. ret.length < instance.options.choices ; i++) {
  2306. var elem = instance.options.array[i];
  2307. var foundPos = instance.options.ignoreCase ?
  2308. elem.toLowerCase().indexOf(entry.toLowerCase()) :
  2309. elem.indexOf(entry);
  2310. while (foundPos != -1) {
  2311. if (foundPos == 0 && elem.length != entry.length) {
  2312. ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
  2313. elem.substr(entry.length) + "</li>");
  2314. break;
  2315. } else if (entry.length >= instance.options.partialChars &&
  2316. instance.options.partialSearch && foundPos != -1) {
  2317. if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
  2318. partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
  2319. elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
  2320. foundPos + entry.length) + "</li>");
  2321. break;
  2322. }
  2323. }
  2324. foundPos = instance.options.ignoreCase ?
  2325. elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
  2326. elem.indexOf(entry, foundPos + 1);
  2327. }
  2328. }
  2329. if (partial.length)
  2330. ret = ret.concat(partial.slice(0, instance.options.choices - ret.length));
  2331. return "<ul>" + ret.join('') + "</ul>";
  2332. }
  2333. }, options || { });
  2334. }
  2335. });
  2336. // AJAX in-place editor and collection editor
  2337. // Full rewrite by Christophe Porteneuve <tdd@tddsworld.com> (April 2007).
  2338. // Use this if you notice weird scrolling problems on some browsers,
  2339. // the DOM might be a bit confused when this gets called so do this
  2340. // waits 1 ms (with setTimeout) until it does the activation
  2341. Field.scrollFreeActivate = function(field) {
  2342. setTimeout(function() {
  2343. Field.activate(field);
  2344. }, 1);
  2345. };
  2346. Ajax.InPlaceEditor = Class.create({
  2347. initialize: function(element, url, options) {
  2348. this.url = url;
  2349. this.element = element = $(element);
  2350. this.prepareOptions();
  2351. this._controls = { };
  2352. arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!
  2353. Object.extend(this.options, options || { });
  2354. if (!this.options.formId && this.element.id) {
  2355. this.options.formId = this.element.id + '-inplaceeditor';
  2356. if ($(this.options.formId))
  2357. this.options.formId = '';
  2358. }
  2359. if (this.options.externalControl)
  2360. this.options.externalControl = $(this.options.externalControl);
  2361. if (!this.options.externalControl)
  2362. this.options.externalControlOnly = false;
  2363. this._originalBackground = this.element.getStyle('background-color') || 'transparent';
  2364. this.element.title = this.options.clickToEditText;
  2365. this._boundCancelHandler = this.handleFormCancellation.bind(this);
  2366. this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);
  2367. this._boundFailureHandler = this.handleAJAXFailure.bind(this);
  2368. this._boundSubmitHandler = this.handleFormSubmission.bind(this);
  2369. this._boundWrapperHandler = this.wrapUp.bind(this);
  2370. this.registerListeners();
  2371. },
  2372. checkForEscapeOrReturn: function(e) {
  2373. if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
  2374. if (Event.KEY_ESC == e.keyCode)
  2375. this.handleFormCancellation(e);
  2376. else if (Event.KEY_RETURN == e.keyCode)
  2377. this.handleFormSubmission(e);
  2378. },
  2379. createControl: function(mode, handler, extraClasses) {
  2380. var control = this.options[mode + 'Control'];
  2381. var text = this.options[mode + 'Text'];
  2382. if ('button' == control) {
  2383. var btn = document.createElement('input');
  2384. btn.type = 'submit';
  2385. btn.value = text;
  2386. btn.className = 'editor_' + mode + '_button';
  2387. if ('cancel' == mode)
  2388. btn.onclick = this._boundCancelHandler;
  2389. this._form.appendChild(btn);
  2390. this._controls[mode] = btn;
  2391. } else if ('link' == control) {
  2392. var link = document.createElement('a');
  2393. link.href = '#';
  2394. link.appendChild(document.createTextNode(text));
  2395. link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;
  2396. link.className = 'editor_' + mode + '_link';
  2397. if (extraClasses)
  2398. link.className += ' ' + extraClasses;
  2399. this._form.appendChild(link);
  2400. this._controls[mode] = link;
  2401. }
  2402. },
  2403. createEditField: function() {
  2404. var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());
  2405. var fld;
  2406. if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) {
  2407. fld = document.createElement('input');
  2408. fld.type = 'text';
  2409. var size = this.options.size || this.options.cols || 0;
  2410. if (0 < size) fld.size = size;
  2411. } else {
  2412. fld = document.createElement('textarea');
  2413. fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows);
  2414. fld.cols = this.options.cols || 40;
  2415. }
  2416. fld.name = this.options.paramName;
  2417. fld.value = text; // No HTML breaks conversion anymore
  2418. fld.className = 'editor_field';
  2419. if (this.options.submitOnBlur)
  2420. fld.onblur = this._boundSubmitHandler;
  2421. this._controls.editor = fld;
  2422. if (this.options.loadTextURL)
  2423. this.loadExternalText();
  2424. this._form.appendChild(this._controls.editor);
  2425. },
  2426. createForm: function() {
  2427. var ipe = this;
  2428. function addText(mode, condition) {
  2429. var text = ipe.options['text' + mode + 'Controls'];
  2430. if (!text || condition === false) return;
  2431. ipe._form.appendChild(document.createTextNode(text));
  2432. };
  2433. this._form = $(document.createElement('form'));
  2434. this._form.id = this.options.formId;
  2435. this._form.addClassName(this.options.formClassName);
  2436. this._form.onsubmit = this._boundSubmitHandler;
  2437. this.createEditField();
  2438. if ('textarea' == this._controls.editor.tagName.toLowerCase())
  2439. this._form.appendChild(document.createElement('br'));
  2440. if (this.options.onFormCustomization)
  2441. this.options.onFormCustomization(this, this._form);
  2442. addText('Before', this.options.okControl || this.options.cancelControl);
  2443. this.createControl('ok', this._boundSubmitHandler);
  2444. addText('Between', this.options.okControl && this.options.cancelControl);
  2445. this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
  2446. addText('After', this.options.okControl || this.options.cancelControl);
  2447. },
  2448. destroy: function() {
  2449. if (this._oldInnerHTML)
  2450. this.element.innerHTML = this._oldInnerHTML;
  2451. this.leaveEditMode();
  2452. this.unregisterListeners();
  2453. },
  2454. enterEditMode: function(e) {
  2455. if (this._saving || this._editing) return;
  2456. this._editing = true;
  2457. this.triggerCallback('onEnterEditMode');
  2458. if (this.options.externalControl)
  2459. this.options.externalControl.hide();
  2460. this.element.hide();
  2461. this.createForm();
  2462. this.element.parentNode.insertBefore(this._form, this.element);
  2463. if (!this.options.loadTextURL)
  2464. this.postProcessEditField();
  2465. if (e) Event.stop(e);
  2466. },
  2467. enterHover: function(e) {
  2468. if (this.options.hoverClassName)
  2469. this.element.addClassName(this.options.hoverClassName);
  2470. if (this._saving) return;
  2471. this.triggerCallback('onEnterHover');
  2472. },
  2473. getText: function() {
  2474. return this.element.innerHTML.unescapeHTML();
  2475. },
  2476. handleAJAXFailure: function(transport) {
  2477. this.triggerCallback('onFailure', transport);
  2478. if (this._oldInnerHTML) {
  2479. this.element.innerHTML = this._oldInnerHTML;
  2480. this._oldInnerHTML = null;
  2481. }
  2482. },
  2483. handleFormCancellation: function(e) {
  2484. this.wrapUp();
  2485. if (e) Event.stop(e);
  2486. },
  2487. handleFormSubmission: function(e) {
  2488. var form = this._form;
  2489. var value = $F(this._controls.editor);
  2490. this.prepareSubmission();
  2491. var params = this.options.callback(form, value) || '';
  2492. if (Object.isString(params))
  2493. params = params.toQueryParams();
  2494. params.editorId = this.element.id;
  2495. if (this.options.htmlResponse) {
  2496. var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);
  2497. Object.extend(options, {
  2498. parameters: params,
  2499. onComplete: this._boundWrapperHandler,
  2500. onFailure: this._boundFailureHandler
  2501. });
  2502. new Ajax.Updater({ success: this.element }, this.url, options);
  2503. } else {
  2504. var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
  2505. Object.extend(options, {
  2506. parameters: params,
  2507. onComplete: this._boundWrapperHandler,
  2508. onFailure: this._boundFailureHandler
  2509. });
  2510. new Ajax.Request(this.url, options);
  2511. }
  2512. if (e) Event.stop(e);
  2513. },
  2514. leaveEditMode: function() {
  2515. this.element.removeClassName(this.options.savingClassName);
  2516. this.removeForm();
  2517. this.leaveHover();
  2518. this.element.style.backgroundColor = this._originalBackground;
  2519. this.element.show();
  2520. if (this.options.externalControl)
  2521. this.options.externalControl.show();
  2522. this._saving = false;
  2523. this._editing = false;
  2524. this._oldInnerHTML = null;
  2525. this.triggerCallback('onLeaveEditMode');
  2526. },
  2527. leaveHover: function(e) {
  2528. if (this.options.hoverClassName)
  2529. this.element.removeClassName(this.options.hoverClassName);
  2530. if (this._saving) return;
  2531. this.triggerCallback('onLeaveHover');
  2532. },
  2533. loadExternalText: function() {
  2534. this._form.addClassName(this.options.loadingClassName);
  2535. this._controls.editor.disabled = true;
  2536. var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
  2537. Object.extend(options, {
  2538. parameters: 'editorId=' + encodeURIComponent(this.element.id),
  2539. onComplete: Prototype.emptyFunction,
  2540. onSuccess: function(transport) {
  2541. this._form.removeClassName(this.options.loadingClassName);
  2542. var text = transport.responseText;
  2543. if (this.options.stripLoadedTextTags)
  2544. text = text.stripTags();
  2545. this._controls.editor.value = text;
  2546. this._controls.editor.disabled = false;
  2547. this.postProcessEditField();
  2548. }.bind(this),
  2549. onFailure: this._boundFailureHandler
  2550. });
  2551. new Ajax.Request(this.options.loadTextURL, options);
  2552. },
  2553. postProcessEditField: function() {
  2554. var fpc = this.options.fieldPostCreation;
  2555. if (fpc)
  2556. $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();
  2557. },
  2558. prepareOptions: function() {
  2559. this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);
  2560. Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);
  2561. [this._extraDefaultOptions].flatten().compact().each(function(defs) {
  2562. Object.extend(this.options, defs);
  2563. }.bind(this));
  2564. },
  2565. prepareSubmission: function() {
  2566. this._saving = true;
  2567. this.removeForm();
  2568. this.leaveHover();
  2569. this.showSaving();
  2570. },
  2571. registerListeners: function() {
  2572. this._listeners = { };
  2573. var listener;
  2574. $H(Ajax.InPlaceEditor.Listeners).each(function(pair) {
  2575. listener = this[pair.value].bind(this);
  2576. this._listeners[pair.key] = listener;
  2577. if (!this.options.externalControlOnly)
  2578. this.element.observe(pair.key, listener);
  2579. if (this.options.externalControl)
  2580. this.options.externalControl.observe(pair.key, listener);
  2581. }.bind(this));
  2582. },
  2583. removeForm: function() {
  2584. if (!this._form) return;
  2585. this._form.remove();
  2586. this._form = null;
  2587. this._controls = { };
  2588. },
  2589. showSaving: function() {
  2590. this._oldInnerHTML = this.element.innerHTML;
  2591. this.element.innerHTML = this.options.savingText;
  2592. this.element.addClassName(this.options.savingClassName);
  2593. this.element.style.backgroundColor = this._originalBackground;
  2594. this.element.show();
  2595. },
  2596. triggerCallback: function(cbName, arg) {
  2597. if ('function' == typeof this.options[cbName]) {
  2598. this.options[cbName](this, arg);
  2599. }
  2600. },
  2601. unregisterListeners: function() {
  2602. $H(this._listeners).each(function(pair) {
  2603. if (!this.options.externalControlOnly)
  2604. this.element.stopObserving(pair.key, pair.value);
  2605. if (this.options.externalControl)
  2606. this.options.externalControl.stopObserving(pair.key, pair.value);
  2607. }.bind(this));
  2608. },
  2609. wrapUp: function(transport) {
  2610. this.leaveEditMode();
  2611. // Can't use triggerCallback due to backward compatibility: requires
  2612. // binding + direct element
  2613. this._boundComplete(transport, this.element);
  2614. }
  2615. });
  2616. Object.extend(Ajax.InPlaceEditor.prototype, {
  2617. dispose: Ajax.InPlaceEditor.prototype.destroy
  2618. });
  2619. Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {
  2620. initialize: function($super, element, url, options) {
  2621. this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;
  2622. $super(element, url, options);
  2623. },
  2624. createEditField: function() {
  2625. var list = document.createElement('select');
  2626. list.name = this.options.paramName;
  2627. list.size = 1;
  2628. this._controls.editor = list;
  2629. this._collection = this.options.collection || [];
  2630. if (this.options.loadCollectionURL)
  2631. this.loadCollection();
  2632. else
  2633. this.checkForExternalText();
  2634. this._form.appendChild(this._controls.editor);
  2635. },
  2636. loadCollection: function() {
  2637. this._form.addClassName(this.options.loadingClassName);
  2638. this.showLoadingText(this.options.loadingCollectionText);
  2639. var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
  2640. Object.extend(options, {
  2641. parameters: 'editorId=' + encodeURIComponent(this.element.id),
  2642. onComplete: Prototype.emptyFunction,
  2643. onSuccess: function(transport) {
  2644. var js = transport.responseText.strip();
  2645. if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
  2646. throw('Server returned an invalid collection representation.');
  2647. this._collection = eval(js);
  2648. this.checkForExternalText();
  2649. }.bind(this),
  2650. onFailure: this.onFailure
  2651. });
  2652. new Ajax.Request(this.options.loadCollectionURL, options);
  2653. },
  2654. showLoadingText: function(text) {
  2655. this._controls.editor.disabled = true;
  2656. var tempOption = this._controls.editor.firstChild;
  2657. if (!tempOption) {
  2658. tempOption = document.createElement('option');
  2659. tempOption.value = '';
  2660. this._controls.editor.appendChild(tempOption);
  2661. tempOption.selected = true;
  2662. }
  2663. tempOption.update((text || '').stripScripts().stripTags());
  2664. },
  2665. checkForExternalText: function() {
  2666. this._text = this.getText();
  2667. if (this.options.loadTextURL)
  2668. this.loadExternalText();
  2669. else
  2670. this.buildOptionList();
  2671. },
  2672. loadExternalText: function() {
  2673. this.showLoadingText(this.options.loadingText);
  2674. var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
  2675. Object.extend(options, {
  2676. parameters: 'editorId=' + encodeURIComponent(this.element.id),
  2677. onComplete: Prototype.emptyFunction,
  2678. onSuccess: function(transport) {
  2679. this._text = transport.responseText.strip();
  2680. this.buildOptionList();
  2681. }.bind(this),
  2682. onFailure: this.onFailure
  2683. });
  2684. new Ajax.Request(this.options.loadTextURL, options);
  2685. },
  2686. buildOptionList: function() {
  2687. this._form.removeClassName(this.options.loadingClassName);
  2688. this._collection = this._collection.map(function(entry) {
  2689. return 2 === entry.length ? entry : [entry, entry].flatten();
  2690. });
  2691. var marker = ('value' in this.options) ? this.options.value : this._text;
  2692. var textFound = this._collection.any(function(entry) {
  2693. return entry[0] == marker;
  2694. }.bind(this));
  2695. this._controls.editor.update('');
  2696. var option;
  2697. this._collection.each(function(entry, index) {
  2698. option = document.createElement('option');
  2699. option.value = entry[0];
  2700. option.selected = textFound ? entry[0] == marker : 0 == index;
  2701. option.appendChild(document.createTextNode(entry[1]));
  2702. this._controls.editor.appendChild(option);
  2703. }.bind(this));
  2704. this._controls.editor.disabled = false;
  2705. Field.scrollFreeActivate(this._controls.editor);
  2706. }
  2707. });
  2708. //**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****
  2709. //**** This only exists for a while, in order to let ****
  2710. //**** users adapt to the new API. Read up on the new ****
  2711. //**** API and convert your code to it ASAP! ****
  2712. Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {
  2713. if (!options) return;
  2714. function fallback(name, expr) {
  2715. if (name in options || expr === undefined) return;
  2716. options[name] = expr;
  2717. };
  2718. fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :
  2719. options.cancelLink == options.cancelButton == false ? false : undefined)));
  2720. fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :
  2721. options.okLink == options.okButton == false ? false : undefined)));
  2722. fallback('highlightColor', options.highlightcolor);
  2723. fallback('highlightEndColor', options.highlightendcolor);
  2724. };
  2725. Object.extend(Ajax.InPlaceEditor, {
  2726. DefaultOptions: {
  2727. ajaxOptions: { },
  2728. autoRows: 3, // Use when multi-line w/ rows == 1
  2729. cancelControl: 'link', // 'link'|'button'|false
  2730. cancelText: 'cancel',
  2731. clickToEditText: 'Click to edit',
  2732. externalControl: null, // id|elt
  2733. externalControlOnly: false,
  2734. fieldPostCreation: 'activate', // 'activate'|'focus'|false
  2735. formClassName: 'inplaceeditor-form',
  2736. formId: null, // id|elt
  2737. highlightColor: '#ffff99',
  2738. highlightEndColor: '#ffffff',
  2739. hoverClassName: '',
  2740. htmlResponse: true,
  2741. loadingClassName: 'inplaceeditor-loading',
  2742. loadingText: 'Loading...',
  2743. okControl: 'button', // 'link'|'button'|false
  2744. okText: 'ok',
  2745. paramName: 'value',
  2746. rows: 1, // If 1 and multi-line, uses autoRows
  2747. savingClassName: 'inplaceeditor-saving',
  2748. savingText: 'Saving...',
  2749. size: 0,
  2750. stripLoadedTextTags: false,
  2751. submitOnBlur: false,
  2752. textAfterControls: '',
  2753. textBeforeControls: '',
  2754. textBetweenControls: ''
  2755. },
  2756. DefaultCallbacks: {
  2757. callback: function(form) {
  2758. return Form.serialize(form);
  2759. },
  2760. onComplete: function(transport, element) {
  2761. // For backward compatibility, this one is bound to the IPE, and passes
  2762. // the element directly. It was too often customized, so we don't break it.
  2763. new Effect.Highlight(element, {
  2764. startcolor: this.options.highlightColor, keepBackgroundImage: true });
  2765. },
  2766. onEnterEditMode: null,
  2767. onEnterHover: function(ipe) {
  2768. ipe.element.style.backgroundColor = ipe.options.highlightColor;
  2769. if (ipe._effect)
  2770. ipe._effect.cancel();
  2771. },
  2772. onFailure: function(transport, ipe) {
  2773. alert('Error communication with the server: ' + transport.responseText.stripTags());
  2774. },
  2775. onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls.
  2776. onLeaveEditMode: null,
  2777. onLeaveHover: function(ipe) {
  2778. ipe._effect = new Effect.Highlight(ipe.element, {
  2779. startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,
  2780. restorecolor: ipe._originalBackground, keepBackgroundImage: true
  2781. });
  2782. }
  2783. },
  2784. Listeners: {
  2785. click: 'enterEditMode',
  2786. keydown: 'checkForEscapeOrReturn',
  2787. mouseover: 'enterHover',
  2788. mouseout: 'leaveHover'
  2789. }
  2790. });
  2791. Ajax.InPlaceCollectionEditor.DefaultOptions = {
  2792. loadingCollectionText: 'Loading options...'
  2793. };
  2794. // Delayed observer, like Form.Element.Observer,
  2795. // but waits for delay after last key input
  2796. // Ideal for live-search fields
  2797. Form.Element.DelayedObserver = Class.create({
  2798. initialize: function(element, delay, callback) {
  2799. this.delay = delay || 0.5;
  2800. this.element = $(element);
  2801. this.callback = callback;
  2802. this.timer = null;
  2803. this.lastValue = $F(this.element);
  2804. Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
  2805. },
  2806. delayedListener: function(event) {
  2807. if(this.lastValue == $F(this.element)) return;
  2808. if(this.timer) clearTimeout(this.timer);
  2809. this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
  2810. this.lastValue = $F(this.element);
  2811. },
  2812. onTimerEvent: function() {
  2813. this.timer = null;
  2814. this.callback(this.element, $F(this.element));
  2815. }
  2816. });
  2817. // script.aculo.us builder.js v1.8.2, Tue Nov 18 18:30:58 +0100 2008
  2818. // Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
  2819. //
  2820. // script.aculo.us is freely distributable under the terms of an MIT-style license.
  2821. // For details, see the script.aculo.us web site: http://script.aculo.us/
  2822. var Builder = {
  2823. NODEMAP: {
  2824. AREA: 'map',
  2825. CAPTION: 'table',
  2826. COL: 'table',
  2827. COLGROUP: 'table',
  2828. LEGEND: 'fieldset',
  2829. OPTGROUP: 'select',
  2830. OPTION: 'select',
  2831. PARAM: 'object',
  2832. TBODY: 'table',
  2833. TD: 'table',
  2834. TFOOT: 'table',
  2835. TH: 'table',
  2836. THEAD: 'table',
  2837. TR: 'table'
  2838. },
  2839. // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken,
  2840. // due to a Firefox bug
  2841. node: function(elementName) {
  2842. elementName = elementName.toUpperCase();
  2843. // try innerHTML approach
  2844. var parentTag = this.NODEMAP[elementName] || 'div';
  2845. var parentElement = document.createElement(parentTag);
  2846. try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
  2847. parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
  2848. } catch(e) {}
  2849. var element = parentElement.firstChild || null;
  2850. // see if browser added wrapping tags
  2851. if(element && (element.tagName.toUpperCase() != elementName))
  2852. element = element.getElementsByTagName(elementName)[0];
  2853. // fallback to createElement approach
  2854. if(!element) element = document.createElement(elementName);
  2855. // abort if nothing could be created
  2856. if(!element) return;
  2857. // attributes (or text)
  2858. if(arguments[1])
  2859. if(this._isStringOrNumber(arguments[1]) ||
  2860. (arguments[1] instanceof Array) ||
  2861. arguments[1].tagName) {
  2862. this._children(element, arguments[1]);
  2863. } else {
  2864. var attrs = this._attributes(arguments[1]);
  2865. if(attrs.length) {
  2866. try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
  2867. parentElement.innerHTML = "<" +elementName + " " +
  2868. attrs + "></" + elementName + ">";
  2869. } catch(e) {}
  2870. element = parentElement.firstChild || null;
  2871. // workaround firefox 1.0.X bug
  2872. if(!element) {
  2873. element = document.createElement(elementName);
  2874. for(attr in arguments[1])
  2875. element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
  2876. }
  2877. if(element.tagName.toUpperCase() != elementName)
  2878. element = parentElement.getElementsByTagName(elementName)[0];
  2879. }
  2880. }
  2881. // text, or array of children
  2882. if(arguments[2])
  2883. this._children(element, arguments[2]);
  2884. return $(element);
  2885. },
  2886. _text: function(text) {
  2887. return document.createTextNode(text);
  2888. },
  2889. ATTR_MAP: {
  2890. 'className': 'class',
  2891. 'htmlFor': 'for'
  2892. },
  2893. _attributes: function(attributes) {
  2894. var attrs = [];
  2895. for(attribute in attributes)
  2896. attrs.push((attribute in this.ATTR_MAP ? this.ATTR_MAP[attribute] : attribute) +
  2897. '="' + attributes[attribute].toString().escapeHTML().gsub(/"/,'&quot;') + '"');
  2898. return attrs.join(" ");
  2899. },
  2900. _children: function(element, children) {
  2901. if(children.tagName) {
  2902. element.appendChild(children);
  2903. return;
  2904. }
  2905. if(typeof children=='object') { // array can hold nodes and text
  2906. children.flatten().each( function(e) {
  2907. if(typeof e=='object')
  2908. element.appendChild(e);
  2909. else
  2910. if(Builder._isStringOrNumber(e))
  2911. element.appendChild(Builder._text(e));
  2912. });
  2913. } else
  2914. if(Builder._isStringOrNumber(children))
  2915. element.appendChild(Builder._text(children));
  2916. },
  2917. _isStringOrNumber: function(param) {
  2918. return(typeof param=='string' || typeof param=='number');
  2919. },
  2920. build: function(html) {
  2921. var element = this.node('div');
  2922. $(element).update(html.strip());
  2923. return element.down();
  2924. },
  2925. dump: function(scope) {
  2926. if(typeof scope != 'object' && typeof scope != 'function') scope = window; //global scope
  2927. var tags = ("A ABBR ACRONYM ADDRESS APPLET AREA B BASE BASEFONT BDO BIG BLOCKQUOTE BODY " +
  2928. "BR BUTTON CAPTION CENTER CITE CODE COL COLGROUP DD DEL DFN DIR DIV DL DT EM FIELDSET " +
  2929. "FONT FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME IMG INPUT INS ISINDEX "+
  2930. "KBD LABEL LEGEND LI LINK MAP MENU META NOFRAMES NOSCRIPT OBJECT OL OPTGROUP OPTION P "+
  2931. "PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD "+
  2932. "TEXTAREA TFOOT TH THEAD TITLE TR TT U UL VAR").split(/\s+/);
  2933. tags.each( function(tag){
  2934. scope[tag] = function() {
  2935. return Builder.node.apply(Builder, [tag].concat($A(arguments)));
  2936. };
  2937. });
  2938. }
  2939. };
  2940. // script.aculo.us slider.js v1.8.2, Tue Nov 18 18:30:58 +0100 2008
  2941. // Copyright (c) 2005-2008 Marty Haught, Thomas Fuchs
  2942. //
  2943. // script.aculo.us is freely distributable under the terms of an MIT-style license.
  2944. // For details, see the script.aculo.us web site: http://script.aculo.us/
  2945. if (!Control) var Control = { };
  2946. // options:
  2947. // axis: 'vertical', or 'horizontal' (default)
  2948. //
  2949. // callbacks:
  2950. // onChange(value)
  2951. // onSlide(value)
  2952. Control.Slider = Class.create({
  2953. initialize: function(handle, track, options) {
  2954. var slider = this;
  2955. if (Object.isArray(handle)) {
  2956. this.handles = handle.collect( function(e) { return $(e) });
  2957. } else {
  2958. this.handles = [$(handle)];
  2959. }
  2960. this.track = $(track);
  2961. this.options = options || { };
  2962. this.axis = this.options.axis || 'horizontal';
  2963. this.increment = this.options.increment || 1;
  2964. this.step = parseInt(this.options.step || '1');
  2965. this.range = this.options.range || $R(0,1);
  2966. this.value = 0; // assure backwards compat
  2967. this.values = this.handles.map( function() { return 0 });
  2968. this.spans = this.options.spans ? this.options.spans.map(function(s){ return $(s) }) : false;
  2969. this.options.startSpan = $(this.options.startSpan || null);
  2970. this.options.endSpan = $(this.options.endSpan || null);
  2971. this.restricted = this.options.restricted || false;
  2972. this.maximum = this.options.maximum || this.range.end;
  2973. this.minimum = this.options.minimum || this.range.start;
  2974. // Will be used to align the handle onto the track, if necessary
  2975. this.alignX = parseInt(this.options.alignX || '0');
  2976. this.alignY = parseInt(this.options.alignY || '0');
  2977. this.trackLength = this.maximumOffset() - this.minimumOffset();
  2978. this.handleLength = this.isVertical() ?
  2979. (this.handles[0].offsetHeight != 0 ?
  2980. this.handles[0].offsetHeight : this.handles[0].style.height.replace(/px$/,"")) :
  2981. (this.handles[0].offsetWidth != 0 ? this.handles[0].offsetWidth :
  2982. this.handles[0].style.width.replace(/px$/,""));
  2983. this.active = false;
  2984. this.dragging = false;
  2985. this.disabled = false;
  2986. if (this.options.disabled) this.setDisabled();
  2987. // Allowed values array
  2988. this.allowedValues = this.options.values ? this.options.values.sortBy(Prototype.K) : false;
  2989. if (this.allowedValues) {
  2990. this.minimum = this.allowedValues.min();
  2991. this.maximum = this.allowedValues.max();
  2992. }
  2993. this.eventMouseDown = this.startDrag.bindAsEventListener(this);
  2994. this.eventMouseUp = this.endDrag.bindAsEventListener(this);
  2995. this.eventMouseMove = this.update.bindAsEventListener(this);
  2996. // Initialize handles in reverse (make sure first handle is active)
  2997. this.handles.each( function(h,i) {
  2998. i = slider.handles.length-1-i;
  2999. slider.setValue(parseFloat(
  3000. (Object.isArray(slider.options.sliderValue) ?
  3001. slider.options.sliderValue[i] : slider.options.sliderValue) ||
  3002. slider.range.start), i);
  3003. h.makePositioned().observe("mousedown", slider.eventMouseDown);
  3004. });
  3005. this.track.observe("mousedown", this.eventMouseDown);
  3006. document.observe("mouseup", this.eventMouseUp);
  3007. document.observe("mousemove", this.eventMouseMove);
  3008. this.initialized = true;
  3009. },
  3010. dispose: function() {
  3011. var slider = this;
  3012. Event.stopObserving(this.track, "mousedown", this.eventMouseDown);
  3013. Event.stopObserving(document, "mouseup", this.eventMouseUp);
  3014. Event.stopObserving(document, "mousemove", this.eventMouseMove);
  3015. this.handles.each( function(h) {
  3016. Event.stopObserving(h, "mousedown", slider.eventMouseDown);
  3017. });
  3018. },
  3019. setDisabled: function(){
  3020. this.disabled = true;
  3021. },
  3022. setEnabled: function(){
  3023. this.disabled = false;
  3024. },
  3025. getNearestValue: function(value){
  3026. if (this.allowedValues){
  3027. if (value >= this.allowedValues.max()) return(this.allowedValues.max());
  3028. if (value <= this.allowedValues.min()) return(this.allowedValues.min());
  3029. var offset = Math.abs(this.allowedValues[0] - value);
  3030. var newValue = this.allowedValues[0];
  3031. this.allowedValues.each( function(v) {
  3032. var currentOffset = Math.abs(v - value);
  3033. if (currentOffset <= offset){
  3034. newValue = v;
  3035. offset = currentOffset;
  3036. }
  3037. });
  3038. return newValue;
  3039. }
  3040. if (value > this.range.end) return this.range.end;
  3041. if (value < this.range.start) return this.range.start;
  3042. return value;
  3043. },
  3044. setValue: function(sliderValue, handleIdx){
  3045. if (!this.active) {
  3046. this.activeHandleIdx = handleIdx || 0;
  3047. this.activeHandle = this.handles[this.activeHandleIdx];
  3048. this.updateStyles();
  3049. }
  3050. handleIdx = handleIdx || this.activeHandleIdx || 0;
  3051. if (this.initialized && this.restricted) {
  3052. if ((handleIdx>0) && (sliderValue<this.values[handleIdx-1]))
  3053. sliderValue = this.values[handleIdx-1];
  3054. if ((handleIdx < (this.handles.length-1)) && (sliderValue>this.values[handleIdx+1]))
  3055. sliderValue = this.values[handleIdx+1];
  3056. }
  3057. sliderValue = this.getNearestValue(sliderValue);
  3058. this.values[handleIdx] = sliderValue;
  3059. this.value = this.values[0]; // assure backwards compat
  3060. this.handles[handleIdx].style[this.isVertical() ? 'top' : 'left'] =
  3061. this.translateToPx(sliderValue);
  3062. this.drawSpans();
  3063. if (!this.dragging || !this.event) this.updateFinished();
  3064. },
  3065. setValueBy: function(delta, handleIdx) {
  3066. this.setValue(this.values[handleIdx || this.activeHandleIdx || 0] + delta,
  3067. handleIdx || this.activeHandleIdx || 0);
  3068. },
  3069. translateToPx: function(value) {
  3070. return Math.round(
  3071. ((this.trackLength-this.handleLength)/(this.range.end-this.range.start)) *
  3072. (value - this.range.start)) + "px";
  3073. },
  3074. translateToValue: function(offset) {
  3075. return ((offset/(this.trackLength-this.handleLength) *
  3076. (this.range.end-this.range.start)) + this.range.start);
  3077. },
  3078. getRange: function(range) {
  3079. var v = this.values.sortBy(Prototype.K);
  3080. range = range || 0;
  3081. return $R(v[range],v[range+1]);
  3082. },
  3083. minimumOffset: function(){
  3084. return(this.isVertical() ? this.alignY : this.alignX);
  3085. },
  3086. maximumOffset: function(){
  3087. return(this.isVertical() ?
  3088. (this.track.offsetHeight != 0 ? this.track.offsetHeight :
  3089. this.track.style.height.replace(/px$/,"")) - this.alignY :
  3090. (this.track.offsetWidth != 0 ? this.track.offsetWidth :
  3091. this.track.style.width.replace(/px$/,"")) - this.alignX);
  3092. },
  3093. isVertical: function(){
  3094. return (this.axis == 'vertical');
  3095. },
  3096. drawSpans: function() {
  3097. var slider = this;
  3098. if (this.spans)
  3099. $R(0, this.spans.length-1).each(function(r) { slider.setSpan(slider.spans[r], slider.getRange(r)) });
  3100. if (this.options.startSpan)
  3101. this.setSpan(this.options.startSpan,
  3102. $R(0, this.values.length>1 ? this.getRange(0).min() : this.value ));
  3103. if (this.options.endSpan)
  3104. this.setSpan(this.options.endSpan,
  3105. $R(this.values.length>1 ? this.getRange(this.spans.length-1).max() : this.value, this.maximum));
  3106. },
  3107. setSpan: function(span, range) {
  3108. if (this.isVertical()) {
  3109. span.style.top = this.translateToPx(range.start);
  3110. span.style.height = this.translateToPx(range.end - range.start + this.range.start);
  3111. } else {
  3112. span.style.left = this.translateToPx(range.start);
  3113. span.style.width = this.translateToPx(range.end - range.start + this.range.start);
  3114. }
  3115. },
  3116. updateStyles: function() {
  3117. this.handles.each( function(h){ Element.removeClassName(h, 'selected') });
  3118. Element.addClassName(this.activeHandle, 'selected');
  3119. },
  3120. startDrag: function(event) {
  3121. if (Event.isLeftClick(event)) {
  3122. if (!this.disabled){
  3123. this.active = true;
  3124. var handle = Event.element(event);
  3125. var pointer = [Event.pointerX(event), Event.pointerY(event)];
  3126. var track = handle;
  3127. if (track==this.track) {
  3128. var offsets = Position.cumulativeOffset(this.track);
  3129. this.event = event;
  3130. this.setValue(this.translateToValue(
  3131. (this.isVertical() ? pointer[1]-offsets[1] : pointer[0]-offsets[0])-(this.handleLength/2)
  3132. ));
  3133. var offsets = Position.cumulativeOffset(this.activeHandle);
  3134. this.offsetX = (pointer[0] - offsets[0]);
  3135. this.offsetY = (pointer[1] - offsets[1]);
  3136. } else {
  3137. // find the handle (prevents issues with Safari)
  3138. while((this.handles.indexOf(handle) == -1) && handle.parentNode)
  3139. handle = handle.parentNode;
  3140. if (this.handles.indexOf(handle)!=-1) {
  3141. this.activeHandle = handle;
  3142. this.activeHandleIdx = this.handles.indexOf(this.activeHandle);
  3143. this.updateStyles();
  3144. var offsets = Position.cumulativeOffset(this.activeHandle);
  3145. this.offsetX = (pointer[0] - offsets[0]);
  3146. this.offsetY = (pointer[1] - offsets[1]);
  3147. }
  3148. }
  3149. }
  3150. Event.stop(event);
  3151. }
  3152. },
  3153. update: function(event) {
  3154. if (this.active) {
  3155. if (!this.dragging) this.dragging = true;
  3156. this.draw(event);
  3157. if (Prototype.Browser.WebKit) window.scrollBy(0,0);
  3158. Event.stop(event);
  3159. }
  3160. },
  3161. draw: function(event) {
  3162. var pointer = [Event.pointerX(event), Event.pointerY(event)];
  3163. var offsets = Position.cumulativeOffset(this.track);
  3164. pointer[0] -= this.offsetX + offsets[0];
  3165. pointer[1] -= this.offsetY + offsets[1];
  3166. this.event = event;
  3167. this.setValue(this.translateToValue( this.isVertical() ? pointer[1] : pointer[0] ));
  3168. if (this.initialized && this.options.onSlide)
  3169. this.options.onSlide(this.values.length>1 ? this.values : this.value, this);
  3170. },
  3171. endDrag: function(event) {
  3172. if (this.active && this.dragging) {
  3173. this.finishDrag(event, true);
  3174. Event.stop(event);
  3175. }
  3176. this.active = false;
  3177. this.dragging = false;
  3178. },
  3179. finishDrag: function(event, success) {
  3180. this.active = false;
  3181. this.dragging = false;
  3182. this.updateFinished();
  3183. },
  3184. updateFinished: function() {
  3185. if (this.initialized && this.options.onChange)
  3186. this.options.onChange(this.values.length>1 ? this.values : this.value, this);
  3187. this.event = null;
  3188. }
  3189. });
  3190. Scriptaculous.load();