/testing/selenium-core/lib/scriptaculous/controls.js

http://datanucleus-appengine.googlecode.com/ · JavaScript · 815 lines · 651 code · 79 blank · 85 comment · 131 complexity · 23cef7404ffe6d410b0c235433771d51 MD5 · raw file

  1. // Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
  2. // (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
  3. // (c) 2005 Jon Tirsen (http://www.tirsen.com)
  4. // Contributors:
  5. // Richard Livsey
  6. // Rahul Bhargava
  7. // Rob Wills
  8. //
  9. // See scriptaculous.js for full license.
  10. // Autocompleter.Base handles all the autocompletion functionality
  11. // that's independent of the data source for autocompletion. This
  12. // includes drawing the autocompletion menu, observing keyboard
  13. // and mouse events, and similar.
  14. //
  15. // Specific autocompleters need to provide, at the very least,
  16. // a getUpdatedChoices function that will be invoked every time
  17. // the text inside the monitored textbox changes. This method
  18. // should get the text for which to provide autocompletion by
  19. // invoking this.getToken(), NOT by directly accessing
  20. // this.element.value. This is to allow incremental tokenized
  21. // autocompletion. Specific auto-completion logic (AJAX, etc)
  22. // belongs in getUpdatedChoices.
  23. //
  24. // Tokenized incremental autocompletion is enabled automatically
  25. // when an autocompleter is instantiated with the 'tokens' option
  26. // in the options parameter, e.g.:
  27. // new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
  28. // will incrementally autocomplete with a comma as the token.
  29. // Additionally, ',' in the above example can be replaced with
  30. // a token array, e.g. { tokens: [',', '\n'] } which
  31. // enables autocompletion on multiple tokens. This is most
  32. // useful when one of the tokens is \n (a newline), as it
  33. // allows smart autocompletion after linebreaks.
  34. var Autocompleter = {}
  35. Autocompleter.Base = function() {};
  36. Autocompleter.Base.prototype = {
  37. baseInitialize: function(element, update, options) {
  38. this.element = $(element);
  39. this.update = $(update);
  40. this.hasFocus = false;
  41. this.changed = false;
  42. this.active = false;
  43. this.index = 0;
  44. this.entryCount = 0;
  45. if (this.setOptions)
  46. this.setOptions(options);
  47. else
  48. this.options = options || {};
  49. this.options.paramName = this.options.paramName || this.element.name;
  50. this.options.tokens = this.options.tokens || [];
  51. this.options.frequency = this.options.frequency || 0.4;
  52. this.options.minChars = this.options.minChars || 1;
  53. this.options.onShow = this.options.onShow ||
  54. function(element, update){
  55. if(!update.style.position || update.style.position=='absolute') {
  56. update.style.position = 'absolute';
  57. Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight});
  58. }
  59. Effect.Appear(update,{duration:0.15});
  60. };
  61. this.options.onHide = this.options.onHide ||
  62. function(element, update){ new Effect.Fade(update,{duration:0.15}) };
  63. if (typeof(this.options.tokens) == 'string')
  64. this.options.tokens = new Array(this.options.tokens);
  65. this.observer = null;
  66. this.element.setAttribute('autocomplete','off');
  67. Element.hide(this.update);
  68. Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
  69. Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
  70. },
  71. show: function() {
  72. if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
  73. if(!this.iefix &&
  74. (navigator.appVersion.indexOf('MSIE')>0) &&
  75. (navigator.userAgent.indexOf('Opera')<0) &&
  76. (Element.getStyle(this.update, 'position')=='absolute')) {
  77. new Insertion.After(this.update,
  78. '<iframe id="' + this.update.id + '_iefix" '+
  79. 'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
  80. 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
  81. this.iefix = $(this.update.id+'_iefix');
  82. }
  83. if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
  84. },
  85. fixIEOverlapping: function() {
  86. Position.clone(this.update, this.iefix);
  87. this.iefix.style.zIndex = 1;
  88. this.update.style.zIndex = 2;
  89. Element.show(this.iefix);
  90. },
  91. hide: function() {
  92. this.stopIndicator();
  93. if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
  94. if(this.iefix) Element.hide(this.iefix);
  95. },
  96. startIndicator: function() {
  97. if(this.options.indicator) Element.show(this.options.indicator);
  98. },
  99. stopIndicator: function() {
  100. if(this.options.indicator) Element.hide(this.options.indicator);
  101. },
  102. onKeyPress: function(event) {
  103. if(this.active)
  104. switch(event.keyCode) {
  105. case Event.KEY_TAB:
  106. case Event.KEY_RETURN:
  107. this.selectEntry();
  108. Event.stop(event);
  109. case Event.KEY_ESC:
  110. this.hide();
  111. this.active = false;
  112. Event.stop(event);
  113. return;
  114. case Event.KEY_LEFT:
  115. case Event.KEY_RIGHT:
  116. return;
  117. case Event.KEY_UP:
  118. this.markPrevious();
  119. this.render();
  120. if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
  121. return;
  122. case Event.KEY_DOWN:
  123. this.markNext();
  124. this.render();
  125. if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
  126. return;
  127. }
  128. else
  129. if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
  130. (navigator.appVersion.indexOf('AppleWebKit') > 0 && event.keyCode == 0)) return;
  131. this.changed = true;
  132. this.hasFocus = true;
  133. if(this.observer) clearTimeout(this.observer);
  134. this.observer =
  135. setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
  136. },
  137. activate: function() {
  138. this.changed = false;
  139. this.hasFocus = true;
  140. this.getUpdatedChoices();
  141. },
  142. onHover: function(event) {
  143. var element = Event.findElement(event, 'LI');
  144. if(this.index != element.autocompleteIndex)
  145. {
  146. this.index = element.autocompleteIndex;
  147. this.render();
  148. }
  149. Event.stop(event);
  150. },
  151. onClick: function(event) {
  152. var element = Event.findElement(event, 'LI');
  153. this.index = element.autocompleteIndex;
  154. this.selectEntry();
  155. this.hide();
  156. },
  157. onBlur: function(event) {
  158. // needed to make click events working
  159. setTimeout(this.hide.bind(this), 250);
  160. this.hasFocus = false;
  161. this.active = false;
  162. },
  163. render: function() {
  164. if(this.entryCount > 0) {
  165. for (var i = 0; i < this.entryCount; i++)
  166. this.index==i ?
  167. Element.addClassName(this.getEntry(i),"selected") :
  168. Element.removeClassName(this.getEntry(i),"selected");
  169. if(this.hasFocus) {
  170. this.show();
  171. this.active = true;
  172. }
  173. } else {
  174. this.active = false;
  175. this.hide();
  176. }
  177. },
  178. markPrevious: function() {
  179. if(this.index > 0) this.index--
  180. else this.index = this.entryCount-1;
  181. },
  182. markNext: function() {
  183. if(this.index < this.entryCount-1) this.index++
  184. else this.index = 0;
  185. },
  186. getEntry: function(index) {
  187. return this.update.firstChild.childNodes[index];
  188. },
  189. getCurrentEntry: function() {
  190. return this.getEntry(this.index);
  191. },
  192. selectEntry: function() {
  193. this.active = false;
  194. this.updateElement(this.getCurrentEntry());
  195. },
  196. updateElement: function(selectedElement) {
  197. if (this.options.updateElement) {
  198. this.options.updateElement(selectedElement);
  199. return;
  200. }
  201. var value = '';
  202. if (this.options.select) {
  203. var nodes = document.getElementsByClassName(this.options.select, selectedElement) || [];
  204. if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
  205. } else
  206. value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
  207. var lastTokenPos = this.findLastToken();
  208. if (lastTokenPos != -1) {
  209. var newValue = this.element.value.substr(0, lastTokenPos + 1);
  210. var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
  211. if (whitespace)
  212. newValue += whitespace[0];
  213. this.element.value = newValue + value;
  214. } else {
  215. this.element.value = value;
  216. }
  217. this.element.focus();
  218. if (this.options.afterUpdateElement)
  219. this.options.afterUpdateElement(this.element, selectedElement);
  220. },
  221. updateChoices: function(choices) {
  222. if(!this.changed && this.hasFocus) {
  223. this.update.innerHTML = choices;
  224. Element.cleanWhitespace(this.update);
  225. Element.cleanWhitespace(this.update.firstChild);
  226. if(this.update.firstChild && this.update.firstChild.childNodes) {
  227. this.entryCount =
  228. this.update.firstChild.childNodes.length;
  229. for (var i = 0; i < this.entryCount; i++) {
  230. var entry = this.getEntry(i);
  231. entry.autocompleteIndex = i;
  232. this.addObservers(entry);
  233. }
  234. } else {
  235. this.entryCount = 0;
  236. }
  237. this.stopIndicator();
  238. this.index = 0;
  239. this.render();
  240. }
  241. },
  242. addObservers: function(element) {
  243. Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
  244. Event.observe(element, "click", this.onClick.bindAsEventListener(this));
  245. },
  246. onObserverEvent: function() {
  247. this.changed = false;
  248. if(this.getToken().length>=this.options.minChars) {
  249. this.startIndicator();
  250. this.getUpdatedChoices();
  251. } else {
  252. this.active = false;
  253. this.hide();
  254. }
  255. },
  256. getToken: function() {
  257. var tokenPos = this.findLastToken();
  258. if (tokenPos != -1)
  259. var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
  260. else
  261. var ret = this.element.value;
  262. return /\n/.test(ret) ? '' : ret;
  263. },
  264. findLastToken: function() {
  265. var lastTokenPos = -1;
  266. for (var i=0; i<this.options.tokens.length; i++) {
  267. var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
  268. if (thisTokenPos > lastTokenPos)
  269. lastTokenPos = thisTokenPos;
  270. }
  271. return lastTokenPos;
  272. }
  273. }
  274. Ajax.Autocompleter = Class.create();
  275. Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
  276. initialize: function(element, update, url, options) {
  277. this.baseInitialize(element, update, options);
  278. this.options.asynchronous = true;
  279. this.options.onComplete = this.onComplete.bind(this);
  280. this.options.defaultParams = this.options.parameters || null;
  281. this.url = url;
  282. },
  283. getUpdatedChoices: function() {
  284. entry = encodeURIComponent(this.options.paramName) + '=' +
  285. encodeURIComponent(this.getToken());
  286. this.options.parameters = this.options.callback ?
  287. this.options.callback(this.element, entry) : entry;
  288. if(this.options.defaultParams)
  289. this.options.parameters += '&' + this.options.defaultParams;
  290. new Ajax.Request(this.url, this.options);
  291. },
  292. onComplete: function(request) {
  293. this.updateChoices(request.responseText);
  294. }
  295. });
  296. // The local array autocompleter. Used when you'd prefer to
  297. // inject an array of autocompletion options into the page, rather
  298. // than sending out Ajax queries, which can be quite slow sometimes.
  299. //
  300. // The constructor takes four parameters. The first two are, as usual,
  301. // the id of the monitored textbox, and id of the autocompletion menu.
  302. // The third is the array you want to autocomplete from, and the fourth
  303. // is the options block.
  304. //
  305. // Extra local autocompletion options:
  306. // - choices - How many autocompletion choices to offer
  307. //
  308. // - partialSearch - If false, the autocompleter will match entered
  309. // text only at the beginning of strings in the
  310. // autocomplete array. Defaults to true, which will
  311. // match text at the beginning of any *word* in the
  312. // strings in the autocomplete array. If you want to
  313. // search anywhere in the string, additionally set
  314. // the option fullSearch to true (default: off).
  315. //
  316. // - fullSsearch - Search anywhere in autocomplete array strings.
  317. //
  318. // - partialChars - How many characters to enter before triggering
  319. // a partial match (unlike minChars, which defines
  320. // how many characters are required to do any match
  321. // at all). Defaults to 2.
  322. //
  323. // - ignoreCase - Whether to ignore case when autocompleting.
  324. // Defaults to true.
  325. //
  326. // It's possible to pass in a custom function as the 'selector'
  327. // option, if you prefer to write your own autocompletion logic.
  328. // In that case, the other options above will not apply unless
  329. // you support them.
  330. Autocompleter.Local = Class.create();
  331. Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
  332. initialize: function(element, update, array, options) {
  333. this.baseInitialize(element, update, options);
  334. this.options.array = array;
  335. },
  336. getUpdatedChoices: function() {
  337. this.updateChoices(this.options.selector(this));
  338. },
  339. setOptions: function(options) {
  340. this.options = Object.extend({
  341. choices: 10,
  342. partialSearch: true,
  343. partialChars: 2,
  344. ignoreCase: true,
  345. fullSearch: false,
  346. selector: function(instance) {
  347. var ret = []; // Beginning matches
  348. var partial = []; // Inside matches
  349. var entry = instance.getToken();
  350. var count = 0;
  351. for (var i = 0; i < instance.options.array.length &&
  352. ret.length < instance.options.choices ; i++) {
  353. var elem = instance.options.array[i];
  354. var foundPos = instance.options.ignoreCase ?
  355. elem.toLowerCase().indexOf(entry.toLowerCase()) :
  356. elem.indexOf(entry);
  357. while (foundPos != -1) {
  358. if (foundPos == 0 && elem.length != entry.length) {
  359. ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
  360. elem.substr(entry.length) + "</li>");
  361. break;
  362. } else if (entry.length >= instance.options.partialChars &&
  363. instance.options.partialSearch && foundPos != -1) {
  364. if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
  365. partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
  366. elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
  367. foundPos + entry.length) + "</li>");
  368. break;
  369. }
  370. }
  371. foundPos = instance.options.ignoreCase ?
  372. elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
  373. elem.indexOf(entry, foundPos + 1);
  374. }
  375. }
  376. if (partial.length)
  377. ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
  378. return "<ul>" + ret.join('') + "</ul>";
  379. }
  380. }, options || {});
  381. }
  382. });
  383. // AJAX in-place editor
  384. //
  385. // see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor
  386. // Use this if you notice weird scrolling problems on some browsers,
  387. // the DOM might be a bit confused when this gets called so do this
  388. // waits 1 ms (with setTimeout) until it does the activation
  389. Field.scrollFreeActivate = function(field) {
  390. setTimeout(function() {
  391. Field.activate(field);
  392. }, 1);
  393. }
  394. Ajax.InPlaceEditor = Class.create();
  395. Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99";
  396. Ajax.InPlaceEditor.prototype = {
  397. initialize: function(element, url, options) {
  398. this.url = url;
  399. this.element = $(element);
  400. this.options = Object.extend({
  401. okButton: true,
  402. okText: "ok",
  403. cancelLink: true,
  404. cancelText: "cancel",
  405. savingText: "Saving...",
  406. clickToEditText: "Click to edit",
  407. okText: "ok",
  408. rows: 1,
  409. onComplete: function(transport, element) {
  410. new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
  411. },
  412. onFailure: function(transport) {
  413. alert("Error communicating with the server: " + transport.responseText.stripTags());
  414. },
  415. callback: function(form) {
  416. return Form.serialize(form);
  417. },
  418. handleLineBreaks: true,
  419. loadingText: 'Loading...',
  420. savingClassName: 'inplaceeditor-saving',
  421. loadingClassName: 'inplaceeditor-loading',
  422. formClassName: 'inplaceeditor-form',
  423. highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
  424. highlightendcolor: "#FFFFFF",
  425. externalControl: null,
  426. submitOnBlur: false,
  427. ajaxOptions: {},
  428. evalScripts: false
  429. }, options || {});
  430. if(!this.options.formId && this.element.id) {
  431. this.options.formId = this.element.id + "-inplaceeditor";
  432. if ($(this.options.formId)) {
  433. // there's already a form with that name, don't specify an id
  434. this.options.formId = null;
  435. }
  436. }
  437. if (this.options.externalControl) {
  438. this.options.externalControl = $(this.options.externalControl);
  439. }
  440. this.originalBackground = Element.getStyle(this.element, 'background-color');
  441. if (!this.originalBackground) {
  442. this.originalBackground = "transparent";
  443. }
  444. this.element.title = this.options.clickToEditText;
  445. this.onclickListener = this.enterEditMode.bindAsEventListener(this);
  446. this.mouseoverListener = this.enterHover.bindAsEventListener(this);
  447. this.mouseoutListener = this.leaveHover.bindAsEventListener(this);
  448. Event.observe(this.element, 'click', this.onclickListener);
  449. Event.observe(this.element, 'mouseover', this.mouseoverListener);
  450. Event.observe(this.element, 'mouseout', this.mouseoutListener);
  451. if (this.options.externalControl) {
  452. Event.observe(this.options.externalControl, 'click', this.onclickListener);
  453. Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener);
  454. Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
  455. }
  456. },
  457. enterEditMode: function(evt) {
  458. if (this.saving) return;
  459. if (this.editing) return;
  460. this.editing = true;
  461. this.onEnterEditMode();
  462. if (this.options.externalControl) {
  463. Element.hide(this.options.externalControl);
  464. }
  465. Element.hide(this.element);
  466. this.createForm();
  467. this.element.parentNode.insertBefore(this.form, this.element);
  468. Field.scrollFreeActivate(this.editField);
  469. // stop the event to avoid a page refresh in Safari
  470. if (evt) {
  471. Event.stop(evt);
  472. }
  473. return false;
  474. },
  475. createForm: function() {
  476. this.form = document.createElement("form");
  477. this.form.id = this.options.formId;
  478. Element.addClassName(this.form, this.options.formClassName)
  479. this.form.onsubmit = this.onSubmit.bind(this);
  480. this.createEditField();
  481. if (this.options.textarea) {
  482. var br = document.createElement("br");
  483. this.form.appendChild(br);
  484. }
  485. if (this.options.okButton) {
  486. okButton = document.createElement("input");
  487. okButton.type = "submit";
  488. okButton.value = this.options.okText;
  489. okButton.className = 'editor_ok_button';
  490. this.form.appendChild(okButton);
  491. }
  492. if (this.options.cancelLink) {
  493. cancelLink = document.createElement("a");
  494. cancelLink.href = "#";
  495. cancelLink.appendChild(document.createTextNode(this.options.cancelText));
  496. cancelLink.onclick = this.onclickCancel.bind(this);
  497. cancelLink.className = 'editor_cancel';
  498. this.form.appendChild(cancelLink);
  499. }
  500. },
  501. hasHTMLLineBreaks: function(string) {
  502. if (!this.options.handleLineBreaks) return false;
  503. return string.match(/<br/i) || string.match(/<p>/i);
  504. },
  505. convertHTMLLineBreaks: function(string) {
  506. return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, "");
  507. },
  508. createEditField: function() {
  509. var text;
  510. if(this.options.loadTextURL) {
  511. text = this.options.loadingText;
  512. } else {
  513. text = this.getText();
  514. }
  515. var obj = this;
  516. if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) {
  517. this.options.textarea = false;
  518. var textField = document.createElement("input");
  519. textField.obj = this;
  520. textField.type = "text";
  521. textField.name = "value";
  522. textField.value = text;
  523. textField.style.backgroundColor = this.options.highlightcolor;
  524. textField.className = 'editor_field';
  525. var size = this.options.size || this.options.cols || 0;
  526. if (size != 0) textField.size = size;
  527. if (this.options.submitOnBlur)
  528. textField.onblur = this.onSubmit.bind(this);
  529. this.editField = textField;
  530. } else {
  531. this.options.textarea = true;
  532. var textArea = document.createElement("textarea");
  533. textArea.obj = this;
  534. textArea.name = "value";
  535. textArea.value = this.convertHTMLLineBreaks(text);
  536. textArea.rows = this.options.rows;
  537. textArea.cols = this.options.cols || 40;
  538. textArea.className = 'editor_field';
  539. if (this.options.submitOnBlur)
  540. textArea.onblur = this.onSubmit.bind(this);
  541. this.editField = textArea;
  542. }
  543. if(this.options.loadTextURL) {
  544. this.loadExternalText();
  545. }
  546. this.form.appendChild(this.editField);
  547. },
  548. getText: function() {
  549. return this.element.innerHTML;
  550. },
  551. loadExternalText: function() {
  552. Element.addClassName(this.form, this.options.loadingClassName);
  553. this.editField.disabled = true;
  554. new Ajax.Request(
  555. this.options.loadTextURL,
  556. Object.extend({
  557. asynchronous: true,
  558. onComplete: this.onLoadedExternalText.bind(this)
  559. }, this.options.ajaxOptions)
  560. );
  561. },
  562. onLoadedExternalText: function(transport) {
  563. Element.removeClassName(this.form, this.options.loadingClassName);
  564. this.editField.disabled = false;
  565. this.editField.value = transport.responseText.stripTags();
  566. },
  567. onclickCancel: function() {
  568. this.onComplete();
  569. this.leaveEditMode();
  570. return false;
  571. },
  572. onFailure: function(transport) {
  573. this.options.onFailure(transport);
  574. if (this.oldInnerHTML) {
  575. this.element.innerHTML = this.oldInnerHTML;
  576. this.oldInnerHTML = null;
  577. }
  578. return false;
  579. },
  580. onSubmit: function() {
  581. // onLoading resets these so we need to save them away for the Ajax call
  582. var form = this.form;
  583. var value = this.editField.value;
  584. // do this first, sometimes the ajax call returns before we get a chance to switch on Saving...
  585. // which means this will actually switch on Saving... *after* we've left edit mode causing Saving...
  586. // to be displayed indefinitely
  587. this.onLoading();
  588. if (this.options.evalScripts) {
  589. new Ajax.Request(
  590. this.url, Object.extend({
  591. parameters: this.options.callback(form, value),
  592. onComplete: this.onComplete.bind(this),
  593. onFailure: this.onFailure.bind(this),
  594. asynchronous:true,
  595. evalScripts:true
  596. }, this.options.ajaxOptions));
  597. } else {
  598. new Ajax.Updater(
  599. { success: this.element,
  600. // don't update on failure (this could be an option)
  601. failure: null },
  602. this.url, Object.extend({
  603. parameters: this.options.callback(form, value),
  604. onComplete: this.onComplete.bind(this),
  605. onFailure: this.onFailure.bind(this)
  606. }, this.options.ajaxOptions));
  607. }
  608. // stop the event to avoid a page refresh in Safari
  609. if (arguments.length > 1) {
  610. Event.stop(arguments[0]);
  611. }
  612. return false;
  613. },
  614. onLoading: function() {
  615. this.saving = true;
  616. this.removeForm();
  617. this.leaveHover();
  618. this.showSaving();
  619. },
  620. showSaving: function() {
  621. this.oldInnerHTML = this.element.innerHTML;
  622. this.element.innerHTML = this.options.savingText;
  623. Element.addClassName(this.element, this.options.savingClassName);
  624. this.element.style.backgroundColor = this.originalBackground;
  625. Element.show(this.element);
  626. },
  627. removeForm: function() {
  628. if(this.form) {
  629. if (this.form.parentNode) Element.remove(this.form);
  630. this.form = null;
  631. }
  632. },
  633. enterHover: function() {
  634. if (this.saving) return;
  635. this.element.style.backgroundColor = this.options.highlightcolor;
  636. if (this.effect) {
  637. this.effect.cancel();
  638. }
  639. Element.addClassName(this.element, this.options.hoverClassName)
  640. },
  641. leaveHover: function() {
  642. if (this.options.backgroundColor) {
  643. this.element.style.backgroundColor = this.oldBackground;
  644. }
  645. Element.removeClassName(this.element, this.options.hoverClassName)
  646. if (this.saving) return;
  647. this.effect = new Effect.Highlight(this.element, {
  648. startcolor: this.options.highlightcolor,
  649. endcolor: this.options.highlightendcolor,
  650. restorecolor: this.originalBackground
  651. });
  652. },
  653. leaveEditMode: function() {
  654. Element.removeClassName(this.element, this.options.savingClassName);
  655. this.removeForm();
  656. this.leaveHover();
  657. this.element.style.backgroundColor = this.originalBackground;
  658. Element.show(this.element);
  659. if (this.options.externalControl) {
  660. Element.show(this.options.externalControl);
  661. }
  662. this.editing = false;
  663. this.saving = false;
  664. this.oldInnerHTML = null;
  665. this.onLeaveEditMode();
  666. },
  667. onComplete: function(transport) {
  668. this.leaveEditMode();
  669. this.options.onComplete.bind(this)(transport, this.element);
  670. },
  671. onEnterEditMode: function() {},
  672. onLeaveEditMode: function() {},
  673. dispose: function() {
  674. if (this.oldInnerHTML) {
  675. this.element.innerHTML = this.oldInnerHTML;
  676. }
  677. this.leaveEditMode();
  678. Event.stopObserving(this.element, 'click', this.onclickListener);
  679. Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
  680. Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
  681. if (this.options.externalControl) {
  682. Event.stopObserving(this.options.externalControl, 'click', this.onclickListener);
  683. Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener);
  684. Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener);
  685. }
  686. }
  687. };
  688. Ajax.InPlaceCollectionEditor = Class.create();
  689. Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype);
  690. Object.extend(Ajax.InPlaceCollectionEditor.prototype, {
  691. createEditField: function() {
  692. if (!this.cached_selectTag) {
  693. var selectTag = document.createElement("select");
  694. var collection = this.options.collection || [];
  695. var optionTag;
  696. collection.each(function(e,i) {
  697. optionTag = document.createElement("option");
  698. optionTag.value = (e instanceof Array) ? e[0] : e;
  699. if(this.options.value==optionTag.value) optionTag.selected = true;
  700. optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e));
  701. selectTag.appendChild(optionTag);
  702. }.bind(this));
  703. this.cached_selectTag = selectTag;
  704. }
  705. this.editField = this.cached_selectTag;
  706. if(this.options.loadTextURL) this.loadExternalText();
  707. this.form.appendChild(this.editField);
  708. this.options.callback = function(form, value) {
  709. return "value=" + encodeURIComponent(value);
  710. }
  711. }
  712. });
  713. // Delayed observer, like Form.Element.Observer,
  714. // but waits for delay after last key input
  715. // Ideal for live-search fields
  716. Form.Element.DelayedObserver = Class.create();
  717. Form.Element.DelayedObserver.prototype = {
  718. initialize: function(element, delay, callback) {
  719. this.delay = delay || 0.5;
  720. this.element = $(element);
  721. this.callback = callback;
  722. this.timer = null;
  723. this.lastValue = $F(this.element);
  724. Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
  725. },
  726. delayedListener: function(event) {
  727. if(this.lastValue == $F(this.element)) return;
  728. if(this.timer) clearTimeout(this.timer);
  729. this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
  730. this.lastValue = $F(this.element);
  731. },
  732. onTimerEvent: function() {
  733. this.timer = null;
  734. this.callback(this.element, $F(this.element));
  735. }
  736. };