Controls.js 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389
  1. /***
  2. Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
  3. (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
  4. (c) 2005 Jon Tirsen (http://www.tirsen.com)
  5. Contributors:
  6. Richard Livsey
  7. Rahul Bhargava
  8. Rob Wills
  9. Mochi-ized By Thomas Herve (_firstname_@nimail.org)
  10. See scriptaculous.js for full license.
  11. Autocompleter.Base handles all the autocompletion functionality
  12. that's independent of the data source for autocompletion. This
  13. includes drawing the autocompletion menu, observing keyboard
  14. and mouse events, and similar.
  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. Tokenized incremental autocompletion is enabled automatically
  24. when an autocompleter is instantiated with the 'tokens' option
  25. in the options parameter, e.g.:
  26. new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
  27. will incrementally autocomplete with a comma as the token.
  28. Additionally, ',' in the above example can be replaced with
  29. a token array, e.g. { tokens: [',', '\n'] } which
  30. enables autocompletion on multiple tokens. This is most
  31. useful when one of the tokens is \n (a newline), as it
  32. allows smart autocompletion after linebreaks.
  33. ***/
  34. MochiKit.Base.update(MochiKit.Base, {
  35. ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)',
  36. /** @id MochiKit.Base.stripScripts */
  37. stripScripts: function (str) {
  38. return str.replace(new RegExp(MochiKit.Base.ScriptFragment, 'img'), '');
  39. },
  40. /** @id MochiKit.Base.stripTags */
  41. stripTags: function(str) {
  42. return str.replace(/<\/?[^>]+>/gi, '');
  43. },
  44. /** @id MochiKit.Base.extractScripts */
  45. extractScripts: function (str) {
  46. var matchAll = new RegExp(MochiKit.Base.ScriptFragment, 'img');
  47. var matchOne = new RegExp(MochiKit.Base.ScriptFragment, 'im');
  48. return MochiKit.Base.map(function (scriptTag) {
  49. return (scriptTag.match(matchOne) || ['', ''])[1];
  50. }, str.match(matchAll) || []);
  51. },
  52. /** @id MochiKit.Base.evalScripts */
  53. evalScripts: function (str) {
  54. return MochiKit.Base.map(function (scr) {
  55. eval(scr);
  56. }, MochiKit.Base.extractScripts(str));
  57. }
  58. });
  59. MochiKit.Form = {
  60. /** @id MochiKit.Form.serialize */
  61. serialize: function (form) {
  62. var elements = MochiKit.Form.getElements(form);
  63. var queryComponents = [];
  64. for (var i = 0; i < elements.length; i++) {
  65. var queryComponent = MochiKit.Form.serializeElement(elements[i]);
  66. if (queryComponent) {
  67. queryComponents.push(queryComponent);
  68. }
  69. }
  70. return queryComponents.join('&');
  71. },
  72. /** @id MochiKit.Form.getElements */
  73. getElements: function (form) {
  74. form = MochiKit.DOM.getElement(form);
  75. var elements = [];
  76. for (tagName in MochiKit.Form.Serializers) {
  77. var tagElements = form.getElementsByTagName(tagName);
  78. for (var j = 0; j < tagElements.length; j++) {
  79. elements.push(tagElements[j]);
  80. }
  81. }
  82. return elements;
  83. },
  84. /** @id MochiKit.Form.serializeElement */
  85. serializeElement: function (element) {
  86. element = MochiKit.DOM.getElement(element);
  87. var method = element.tagName.toLowerCase();
  88. var parameter = MochiKit.Form.Serializers[method](element);
  89. if (parameter) {
  90. var key = encodeURIComponent(parameter[0]);
  91. if (key.length === 0) {
  92. return;
  93. }
  94. if (!(parameter[1] instanceof Array)) {
  95. parameter[1] = [parameter[1]];
  96. }
  97. return parameter[1].map(function (value) {
  98. return key + '=' + encodeURIComponent(value);
  99. }).join('&');
  100. }
  101. }
  102. };
  103. MochiKit.Form.Serializers = {
  104. /** @id MochiKit.Form.Serializers.input */
  105. input: function (element) {
  106. switch (element.type.toLowerCase()) {
  107. case 'submit':
  108. case 'hidden':
  109. case 'password':
  110. case 'text':
  111. return MochiKit.Form.Serializers.textarea(element);
  112. case 'checkbox':
  113. case 'radio':
  114. return MochiKit.Form.Serializers.inputSelector(element);
  115. }
  116. return false;
  117. },
  118. /** @id MochiKit.Form.Serializers.inputSelector */
  119. inputSelector: function (element) {
  120. if (element.checked) {
  121. return [element.name, element.value];
  122. }
  123. },
  124. /** @id MochiKit.Form.Serializers.textarea */
  125. textarea: function (element) {
  126. return [element.name, element.value];
  127. },
  128. /** @id MochiKit.Form.Serializers.select */
  129. select: function (element) {
  130. return MochiKit.Form.Serializers[element.type == 'select-one' ?
  131. 'selectOne' : 'selectMany'](element);
  132. },
  133. /** @id MochiKit.Form.Serializers.selectOne */
  134. selectOne: function (element) {
  135. var value = '', opt, index = element.selectedIndex;
  136. if (index >= 0) {
  137. opt = element.options[index];
  138. value = opt.value;
  139. if (!value && !('value' in opt)) {
  140. value = opt.text;
  141. }
  142. }
  143. return [element.name, value];
  144. },
  145. /** @id MochiKit.Form.Serializers.selectMany */
  146. selectMany: function (element) {
  147. var value = [];
  148. for (var i = 0; i < element.length; i++) {
  149. var opt = element.options[i];
  150. if (opt.selected) {
  151. var optValue = opt.value;
  152. if (!optValue && !('value' in opt)) {
  153. optValue = opt.text;
  154. }
  155. value.push(optValue);
  156. }
  157. }
  158. return [element.name, value];
  159. }
  160. };
  161. /** @id Ajax */
  162. var Ajax = {
  163. activeRequestCount: 0
  164. };
  165. Ajax.Responders = {
  166. responders: [],
  167. /** @id Ajax.Responders.register */
  168. register: function (responderToAdd) {
  169. if (MochiKit.Base.find(this.responders, responderToAdd) == -1) {
  170. this.responders.push(responderToAdd);
  171. }
  172. },
  173. /** @id Ajax.Responders.unregister */
  174. unregister: function (responderToRemove) {
  175. this.responders = this.responders.without(responderToRemove);
  176. },
  177. /** @id Ajax.Responders.dispatch */
  178. dispatch: function (callback, request, transport, json) {
  179. MochiKit.Iter.forEach(this.responders, function (responder) {
  180. if (responder[callback] &&
  181. typeof(responder[callback]) == 'function') {
  182. try {
  183. responder[callback].apply(responder, [request, transport, json]);
  184. } catch (e) {}
  185. }
  186. });
  187. }
  188. };
  189. Ajax.Responders.register({
  190. /** @id Ajax.Responders.onCreate */
  191. onCreate: function () {
  192. Ajax.activeRequestCount++;
  193. },
  194. /** @id Ajax.Responders.onComplete */
  195. onComplete: function () {
  196. Ajax.activeRequestCount--;
  197. }
  198. });
  199. /** @id Ajax.Base */
  200. Ajax.Base = function () {};
  201. Ajax.Base.prototype = {
  202. /** @id Ajax.Base.prototype.setOptions */
  203. setOptions: function (options) {
  204. this.options = {
  205. method: 'post',
  206. asynchronous: true,
  207. parameters: ''
  208. }
  209. MochiKit.Base.update(this.options, options || {});
  210. },
  211. /** @id Ajax.Base.prototype.responseIsSuccess */
  212. responseIsSuccess: function () {
  213. return this.transport.status == undefined
  214. || this.transport.status === 0
  215. || (this.transport.status >= 200 && this.transport.status < 300);
  216. },
  217. /** @id Ajax.Base.prototype.responseIsFailure */
  218. responseIsFailure: function () {
  219. return !this.responseIsSuccess();
  220. }
  221. };
  222. /** @id Ajax.Request */
  223. Ajax.Request = function (url, options) {
  224. this.__init__(url, options);
  225. };
  226. /** @id Ajax.Events */
  227. Ajax.Request.Events = ['Uninitialized', 'Loading', 'Loaded',
  228. 'Interactive', 'Complete'];
  229. MochiKit.Base.update(Ajax.Request.prototype, Ajax.Base.prototype);
  230. MochiKit.Base.update(Ajax.Request.prototype, {
  231. __init__: function (url, options) {
  232. this.transport = MochiKit.Async.getXMLHttpRequest();
  233. this.setOptions(options);
  234. this.request(url);
  235. },
  236. /** @id Ajax.Request.prototype.request */
  237. request: function (url) {
  238. var parameters = this.options.parameters || '';
  239. if (parameters.length > 0){
  240. parameters += '&_=';
  241. }
  242. try {
  243. this.url = url;
  244. if (this.options.method == 'get' && parameters.length > 0) {
  245. this.url += (this.url.match(/\?/) ? '&' : '?') + parameters;
  246. }
  247. Ajax.Responders.dispatch('onCreate', this, this.transport);
  248. this.transport.open(this.options.method, this.url,
  249. this.options.asynchronous);
  250. if (this.options.asynchronous) {
  251. this.transport.onreadystatechange = MochiKit.Base.bind(this.onStateChange, this);
  252. setTimeout(MochiKit.Base.bind(function () {
  253. this.respondToReadyState(1);
  254. }, this), 10);
  255. }
  256. this.setRequestHeaders();
  257. var body = this.options.postBody ? this.options.postBody : parameters;
  258. this.transport.send(this.options.method == 'post' ? body : null);
  259. } catch (e) {
  260. this.dispatchException(e);
  261. }
  262. },
  263. /** @id Ajax.Request.prototype.setRequestHeaders */
  264. setRequestHeaders: function () {
  265. var requestHeaders = ['X-Requested-With', 'XMLHttpRequest'];
  266. if (this.options.method == 'post') {
  267. requestHeaders.push('Content-type',
  268. 'application/x-www-form-urlencoded');
  269. /* Force 'Connection: close' for Mozilla browsers to work around
  270. * a bug where XMLHttpRequest sends an incorrect Content-length
  271. * header. See Mozilla Bugzilla #246651.
  272. */
  273. if (this.transport.overrideMimeType) {
  274. requestHeaders.push('Connection', 'close');
  275. }
  276. }
  277. if (this.options.requestHeaders) {
  278. requestHeaders.push.apply(requestHeaders, this.options.requestHeaders);
  279. }
  280. for (var i = 0; i < requestHeaders.length; i += 2) {
  281. this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]);
  282. }
  283. },
  284. /** @id Ajax.Request.prototype.onStateChange */
  285. onStateChange: function () {
  286. var readyState = this.transport.readyState;
  287. if (readyState != 1) {
  288. this.respondToReadyState(this.transport.readyState);
  289. }
  290. },
  291. /** @id Ajax.Request.prototype.header */
  292. header: function (name) {
  293. try {
  294. return this.transport.getResponseHeader(name);
  295. } catch (e) {}
  296. },
  297. /** @id Ajax.Request.prototype.evalJSON */
  298. evalJSON: function () {
  299. try {
  300. return eval(this.header('X-JSON'));
  301. } catch (e) {}
  302. },
  303. /** @id Ajax.Request.prototype.evalResponse */
  304. evalResponse: function () {
  305. try {
  306. return eval(this.transport.responseText);
  307. } catch (e) {
  308. this.dispatchException(e);
  309. }
  310. },
  311. /** @id Ajax.Request.prototype.respondToReadyState */
  312. respondToReadyState: function (readyState) {
  313. var event = Ajax.Request.Events[readyState];
  314. var transport = this.transport, json = this.evalJSON();
  315. if (event == 'Complete') {
  316. try {
  317. (this.options['on' + this.transport.status]
  318. || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]
  319. || MochiKit.Base.noop)(transport, json);
  320. } catch (e) {
  321. this.dispatchException(e);
  322. }
  323. if ((this.header('Content-type') || '').match(/^text\/javascript/i)) {
  324. this.evalResponse();
  325. }
  326. }
  327. try {
  328. (this.options['on' + event] || MochiKit.Base.noop)(transport, json);
  329. Ajax.Responders.dispatch('on' + event, this, transport, json);
  330. } catch (e) {
  331. this.dispatchException(e);
  332. }
  333. /* Avoid memory leak in MSIE: clean up the oncomplete event handler */
  334. if (event == 'Complete') {
  335. this.transport.onreadystatechange = MochiKit.Base.noop;
  336. }
  337. },
  338. /** @id Ajax.Request.prototype.dispatchException */
  339. dispatchException: function (exception) {
  340. (this.options.onException || MochiKit.Base.noop)(this, exception);
  341. Ajax.Responders.dispatch('onException', this, exception);
  342. }
  343. });
  344. /** @id Ajax.Updater */
  345. Ajax.Updater = function (container, url, options) {
  346. this.__init__(container, url, options);
  347. };
  348. MochiKit.Base.update(Ajax.Updater.prototype, Ajax.Request.prototype);
  349. MochiKit.Base.update(Ajax.Updater.prototype, {
  350. __init__: function (container, url, options) {
  351. this.containers = {
  352. success: container.success ? MochiKit.DOM.getElement(container.success) : MochiKit.DOM.getElement(container),
  353. failure: container.failure ? MochiKit.DOM.getElement(container.failure) :
  354. (container.success ? null : MochiKit.DOM.getElement(container))
  355. }
  356. this.transport = MochiKit.Async.getXMLHttpRequest();
  357. this.setOptions(options);
  358. var onComplete = this.options.onComplete || MochiKit.Base.noop;
  359. this.options.onComplete = MochiKit.Base.bind(function (transport, object) {
  360. this.updateContent();
  361. onComplete(transport, object);
  362. }, this);
  363. this.request(url);
  364. },
  365. /** @id Ajax.Updater.prototype.updateContent */
  366. updateContent: function () {
  367. var receiver = this.responseIsSuccess() ?
  368. this.containers.success : this.containers.failure;
  369. var response = this.transport.responseText;
  370. if (!this.options.evalScripts) {
  371. response = MochiKit.Base.stripScripts(response);
  372. }
  373. if (receiver) {
  374. if (this.options.insertion) {
  375. new this.options.insertion(receiver, response);
  376. } else {
  377. MochiKit.DOM.getElement(receiver).innerHTML =
  378. MochiKit.Base.stripScripts(response);
  379. setTimeout(function () {
  380. MochiKit.Base.evalScripts(response);
  381. }, 10);
  382. }
  383. }
  384. if (this.responseIsSuccess()) {
  385. if (this.onComplete) {
  386. setTimeout(MochiKit.Base.bind(this.onComplete, this), 10);
  387. }
  388. }
  389. }
  390. });
  391. /** @id Field */
  392. var Field = {
  393. /** @id clear */
  394. clear: function () {
  395. for (var i = 0; i < arguments.length; i++) {
  396. MochiKit.DOM.getElement(arguments[i]).value = '';
  397. }
  398. },
  399. /** @id focus */
  400. focus: function (element) {
  401. MochiKit.DOM.getElement(element).focus();
  402. },
  403. /** @id present */
  404. present: function () {
  405. for (var i = 0; i < arguments.length; i++) {
  406. if (MochiKit.DOM.getElement(arguments[i]).value == '') {
  407. return false;
  408. }
  409. }
  410. return true;
  411. },
  412. /** @id select */
  413. select: function (element) {
  414. MochiKit.DOM.getElement(element).select();
  415. },
  416. /** @id activate */
  417. activate: function (element) {
  418. element = MochiKit.DOM.getElement(element);
  419. element.focus();
  420. if (element.select) {
  421. element.select();
  422. }
  423. },
  424. /** @id scrollFreeActivate */
  425. scrollFreeActivate: function (field) {
  426. setTimeout(function () {
  427. Field.activate(field);
  428. }, 1);
  429. }
  430. };
  431. /** @id Autocompleter */
  432. var Autocompleter = {};
  433. /** @id Autocompleter.Base */
  434. Autocompleter.Base = function () {};
  435. Autocompleter.Base.prototype = {
  436. /** @id Autocompleter.Base.prototype.baseInitialize */
  437. baseInitialize: function (element, update, options) {
  438. this.element = MochiKit.DOM.getElement(element);
  439. this.update = MochiKit.DOM.getElement(update);
  440. this.hasFocus = false;
  441. this.changed = false;
  442. this.active = false;
  443. this.index = 0;
  444. this.entryCount = 0;
  445. if (this.setOptions) {
  446. this.setOptions(options);
  447. }
  448. else {
  449. this.options = options || {};
  450. }
  451. this.options.paramName = this.options.paramName || this.element.name;
  452. this.options.tokens = this.options.tokens || [];
  453. this.options.frequency = this.options.frequency || 0.4;
  454. this.options.minChars = this.options.minChars || 1;
  455. this.options.onShow = this.options.onShow || function (element, update) {
  456. if (!update.style.position || update.style.position == 'absolute') {
  457. update.style.position = 'absolute';
  458. MochiKit.Position.clone(element, update, {
  459. setHeight: false,
  460. offsetTop: element.offsetHeight
  461. });
  462. }
  463. MochiKit.Visual.appear(update, {duration:0.15});
  464. };
  465. this.options.onHide = this.options.onHide || function (element, update) {
  466. MochiKit.Visual.fade(update, {duration: 0.15});
  467. };
  468. if (typeof(this.options.tokens) == 'string') {
  469. this.options.tokens = new Array(this.options.tokens);
  470. }
  471. this.observer = null;
  472. this.element.setAttribute('autocomplete', 'off');
  473. MochiKit.Style.hideElement(this.update);
  474. MochiKit.Signal.connect(this.element, 'onblur', this, this.onBlur);
  475. MochiKit.Signal.connect(this.element, 'onkeypress', this, this.onKeyPress, this);
  476. },
  477. /** @id Autocompleter.Base.prototype.show */
  478. show: function () {
  479. if (MochiKit.Style.getStyle(this.update, 'display') == 'none') {
  480. this.options.onShow(this.element, this.update);
  481. }
  482. if (!this.iefix && /MSIE/.test(navigator.userAgent &&
  483. (MochiKit.Style.getStyle(this.update, 'position') == 'absolute')) {
  484. new Insertion.After(this.update,
  485. '<iframe id="' + this.update.id + '_iefix" '+
  486. 'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
  487. 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
  488. this.iefix = MochiKit.DOM.getElement(this.update.id + '_iefix');
  489. }
  490. if (this.iefix) {
  491. setTimeout(MochiKit.Base.bind(this.fixIEOverlapping, this), 50);
  492. }
  493. },
  494. /** @id Autocompleter.Base.prototype.fixIEOverlapping */
  495. fixIEOverlapping: function () {
  496. MochiKit.Position.clone(this.update, this.iefix);
  497. this.iefix.style.zIndex = 1;
  498. this.update.style.zIndex = 2;
  499. MochiKit.Style.showElement(this.iefix);
  500. },
  501. /** @id Autocompleter.Base.prototype.hide */
  502. hide: function () {
  503. this.stopIndicator();
  504. if (MochiKit.Style.getStyle(this.update, 'display') != 'none') {
  505. this.options.onHide(this.element, this.update);
  506. }
  507. if (this.iefix) {
  508. MochiKit.Style.hideElement(this.iefix);
  509. }
  510. },
  511. /** @id Autocompleter.Base.prototype.startIndicator */
  512. startIndicator: function () {
  513. if (this.options.indicator) {
  514. MochiKit.Style.showElement(this.options.indicator);
  515. }
  516. },
  517. /** @id Autocompleter.Base.prototype.stopIndicator */
  518. stopIndicator: function () {
  519. if (this.options.indicator) {
  520. MochiKit.Style.hideElement(this.options.indicator);
  521. }
  522. },
  523. /** @id Autocompleter.Base.prototype.onKeyPress */
  524. onKeyPress: function (event) {
  525. if (this.active) {
  526. if (event.key().string == "KEY_TAB" || event.key().string == "KEY_RETURN") {
  527. this.selectEntry();
  528. MochiKit.Event.stop(event);
  529. } else if (event.key().string == "KEY_ESCAPE") {
  530. this.hide();
  531. this.active = false;
  532. MochiKit.Event.stop(event);
  533. return;
  534. } else if (event.key().string == "KEY_LEFT" || event.key().string == "KEY_RIGHT") {
  535. return;
  536. } else if (event.key().string == "KEY_UP") {
  537. this.markPrevious();
  538. this.render();
  539. if (/AppleWebKit'/.test(navigator.appVersion)) {
  540. event.stop();
  541. }
  542. return;
  543. } else if (event.key().string == "KEY_DOWN") {
  544. this.markNext();
  545. this.render();
  546. if (/AppleWebKit'/.test(navigator.appVersion)) {
  547. event.stop();
  548. }
  549. return;
  550. }
  551. } else {
  552. if (event.key().string == "KEY_TAB" || event.key().string == "KEY_RETURN") {
  553. return;
  554. }
  555. }
  556. this.changed = true;
  557. this.hasFocus = true;
  558. if (this.observer) {
  559. clearTimeout(this.observer);
  560. }
  561. this.observer = setTimeout(MochiKit.Base.bind(this.onObserverEvent, this),
  562. this.options.frequency*1000);
  563. },
  564. /** @id Autocompleter.Base.prototype.findElement */
  565. findElement: function (event, tagName) {
  566. var element = event.target;
  567. while (element.parentNode && (!element.tagName ||
  568. (element.tagName.toUpperCase() != tagName.toUpperCase()))) {
  569. element = element.parentNode;
  570. }
  571. return element;
  572. },
  573. /** @id Autocompleter.Base.prototype.hover */
  574. onHover: function (event) {
  575. var element = this.findElement(event, 'LI');
  576. if (this.index != element.autocompleteIndex) {
  577. this.index = element.autocompleteIndex;
  578. this.render();
  579. }
  580. event.stop();
  581. },
  582. /** @id Autocompleter.Base.prototype.onClick */
  583. onClick: function (event) {
  584. var element = this.findElement(event, 'LI');
  585. this.index = element.autocompleteIndex;
  586. this.selectEntry();
  587. this.hide();
  588. },
  589. /** @id Autocompleter.Base.prototype.onBlur */
  590. onBlur: function (event) {
  591. // needed to make click events working
  592. setTimeout(MochiKit.Base.bind(this.hide, this), 250);
  593. this.hasFocus = false;
  594. this.active = false;
  595. },
  596. /** @id Autocompleter.Base.prototype.render */
  597. render: function () {
  598. if (this.entryCount > 0) {
  599. for (var i = 0; i < this.entryCount; i++) {
  600. this.index == i ?
  601. MochiKit.DOM.addElementClass(this.getEntry(i), 'selected') :
  602. MochiKit.DOM.removeElementClass(this.getEntry(i), 'selected');
  603. }
  604. if (this.hasFocus) {
  605. this.show();
  606. this.active = true;
  607. }
  608. } else {
  609. this.active = false;
  610. this.hide();
  611. }
  612. },
  613. /** @id Autocompleter.Base.prototype.markPrevious */
  614. markPrevious: function () {
  615. if (this.index > 0) {
  616. this.index--
  617. } else {
  618. this.index = this.entryCount-1;
  619. }
  620. },
  621. /** @id Autocompleter.Base.prototype.markNext */
  622. markNext: function () {
  623. if (this.index < this.entryCount-1) {
  624. this.index++
  625. } else {
  626. this.index = 0;
  627. }
  628. },
  629. /** @id Autocompleter.Base.prototype.getEntry */
  630. getEntry: function (index) {
  631. return this.update.firstChild.childNodes[index];
  632. },
  633. /** @id Autocompleter.Base.prototype.getCurrentEntry */
  634. getCurrentEntry: function () {
  635. return this.getEntry(this.index);
  636. },
  637. /** @id Autocompleter.Base.prototype.selectEntry */
  638. selectEntry: function () {
  639. this.active = false;
  640. this.updateElement(this.getCurrentEntry());
  641. },
  642. /** @id Autocompleter.Base.prototype.collectTextNodesIgnoreClass */
  643. collectTextNodesIgnoreClass: function (element, className) {
  644. return MochiKit.Base.flattenArray(MochiKit.Base.map(function (node) {
  645. if (node.nodeType == 3) {
  646. return node.nodeValue;
  647. } else if (node.hasChildNodes() && !MochiKit.DOM.hasElementClass(node, className)) {
  648. return this.collectTextNodesIgnoreClass(node, className);
  649. }
  650. return '';
  651. }, MochiKit.DOM.getElement(element).childNodes)).join('');
  652. },
  653. /** @id Autocompleter.Base.prototype.updateElement */
  654. updateElement: function (selectedElement) {
  655. if (this.options.updateElement) {
  656. this.options.updateElement(selectedElement);
  657. return;
  658. }
  659. var value = '';
  660. if (this.options.select) {
  661. var nodes = document.getElementsByClassName(this.options.select, selectedElement) || [];
  662. if (nodes.length > 0) {
  663. value = MochiKit.DOM.scrapeText(nodes[0]);
  664. }
  665. } else {
  666. value = this.collectTextNodesIgnoreClass(selectedElement, 'informal');
  667. }
  668. var lastTokenPos = this.findLastToken();
  669. if (lastTokenPos != -1) {
  670. var newValue = this.element.value.substr(0, lastTokenPos + 1);
  671. var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
  672. if (whitespace) {
  673. newValue += whitespace[0];
  674. }
  675. this.element.value = newValue + value;
  676. } else {
  677. this.element.value = value;
  678. }
  679. this.element.focus();
  680. if (this.options.afterUpdateElement) {
  681. this.options.afterUpdateElement(this.element, selectedElement);
  682. }
  683. },
  684. /** @id Autocompleter.Base.prototype.updateChoices */
  685. updateChoices: function (choices) {
  686. if (!this.changed && this.hasFocus) {
  687. this.update.innerHTML = choices;
  688. var d = MochiKit.DOM;
  689. d.removeEmptyTextNodes(this.update);
  690. d.removeEmptyTextNodes(this.update.firstChild);
  691. if (this.update.firstChild && this.update.firstChild.childNodes) {
  692. this.entryCount = this.update.firstChild.childNodes.length;
  693. for (var i = 0; i < this.entryCount; i++) {
  694. var entry = this.getEntry(i);
  695. entry.autocompleteIndex = i;
  696. this.addObservers(entry);
  697. }
  698. } else {
  699. this.entryCount = 0;
  700. }
  701. this.stopIndicator();
  702. this.index = 0;
  703. this.render();
  704. }
  705. },
  706. /** @id Autocompleter.Base.prototype.addObservers */
  707. addObservers: function (element) {
  708. MochiKit.Signal.connect(element, 'onmouseover', this, this.onHover);
  709. MochiKit.Signal.connect(element, 'onclick', this, this.onClick);
  710. },
  711. /** @id Autocompleter.Base.prototype.onObserverEvent */
  712. onObserverEvent: function () {
  713. this.changed = false;
  714. if (this.getToken().length >= this.options.minChars) {
  715. this.startIndicator();
  716. this.getUpdatedChoices();
  717. } else {
  718. this.active = false;
  719. this.hide();
  720. }
  721. },
  722. /** @id Autocompleter.Base.prototype.getToken */
  723. getToken: function () {
  724. var tokenPos = this.findLastToken();
  725. if (tokenPos != -1) {
  726. var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
  727. } else {
  728. var ret = this.element.value;
  729. }
  730. return /\n/.test(ret) ? '' : ret;
  731. },
  732. /** @id Autocompleter.Base.prototype.findLastToken */
  733. findLastToken: function () {
  734. var lastTokenPos = -1;
  735. for (var i = 0; i < this.options.tokens.length; i++) {
  736. var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
  737. if (thisTokenPos > lastTokenPos) {
  738. lastTokenPos = thisTokenPos;
  739. }
  740. }
  741. return lastTokenPos;
  742. }
  743. }
  744. /** @id Ajax.Autocompleter */
  745. Ajax.Autocompleter = function (element, update, url, options) {
  746. this.__init__(element, update, url, options);
  747. };
  748. MochiKit.Base.update(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype);
  749. MochiKit.Base.update(Ajax.Autocompleter.prototype, {
  750. __init__: function (element, update, url, options) {
  751. this.baseInitialize(element, update, options);
  752. this.options.asynchronous = true;
  753. this.options.onComplete = MochiKit.Base.bind(this.onComplete, this);
  754. this.options.defaultParams = this.options.parameters || null;
  755. this.url = url;
  756. },
  757. /** @id Ajax.Autocompleter.prototype.getUpdatedChoices */
  758. getUpdatedChoices: function () {
  759. var entry = encodeURIComponent(this.options.paramName) + '=' +
  760. encodeURIComponent(this.getToken());
  761. this.options.parameters = this.options.callback ?
  762. this.options.callback(this.element, entry) : entry;
  763. if (this.options.defaultParams) {
  764. this.options.parameters += '&' + this.options.defaultParams;
  765. }
  766. new Ajax.Request(this.url, this.options);
  767. },
  768. /** @id Ajax.Autocompleter.prototype.onComplete */
  769. onComplete: function (request) {
  770. this.updateChoices(request.responseText);
  771. }
  772. });
  773. /***
  774. The local array autocompleter. Used when you'd prefer to
  775. inject an array of autocompletion options into the page, rather
  776. than sending out Ajax queries, which can be quite slow sometimes.
  777. The constructor takes four parameters. The first two are, as usual,
  778. the id of the monitored textbox, and id of the autocompletion menu.
  779. The third is the array you want to autocomplete from, and the fourth
  780. is the options block.
  781. Extra local autocompletion options:
  782. - choices - How many autocompletion choices to offer
  783. - partialSearch - If false, the autocompleter will match entered
  784. text only at the beginning of strings in the
  785. autocomplete array. Defaults to true, which will
  786. match text at the beginning of any *word* in the
  787. strings in the autocomplete array. If you want to
  788. search anywhere in the string, additionally set
  789. the option fullSearch to true (default: off).
  790. - fullSsearch - Search anywhere in autocomplete array strings.
  791. - partialChars - How many characters to enter before triggering
  792. a partial match (unlike minChars, which defines
  793. how many characters are required to do any match
  794. at all). Defaults to 2.
  795. - ignoreCase - Whether to ignore case when autocompleting.
  796. Defaults to true.
  797. It's possible to pass in a custom function as the 'selector'
  798. option, if you prefer to write your own autocompletion logic.
  799. In that case, the other options above will not apply unless
  800. you support them.
  801. ***/
  802. /** @id Autocompleter.Local */
  803. Autocompleter.Local = function (element, update, array, options) {
  804. this.__init__(element, update, array, options);
  805. };
  806. MochiKit.Base.update(Autocompleter.Local.prototype, Autocompleter.Base.prototype);
  807. MochiKit.Base.update(Autocompleter.Local.prototype, {
  808. __init__: function (element, update, array, options) {
  809. this.baseInitialize(element, update, options);
  810. this.options.array = array;
  811. },
  812. /** @id Autocompleter.Local.prototype.getUpdatedChoices */
  813. getUpdatedChoices: function () {
  814. this.updateChoices(this.options.selector(this));
  815. },
  816. /** @id Autocompleter.Local.prototype.setOptions */
  817. setOptions: function (options) {
  818. this.options = MochiKit.Base.update({
  819. choices: 10,
  820. partialSearch: true,
  821. partialChars: 2,
  822. ignoreCase: true,
  823. fullSearch: false,
  824. selector: function (instance) {
  825. var ret = []; // Beginning matches
  826. var partial = []; // Inside matches
  827. var entry = instance.getToken();
  828. var count = 0;
  829. for (var i = 0; i < instance.options.array.length &&
  830. ret.length < instance.options.choices ; i++) {
  831. var elem = instance.options.array[i];
  832. var foundPos = instance.options.ignoreCase ?
  833. elem.toLowerCase().indexOf(entry.toLowerCase()) :
  834. elem.indexOf(entry);
  835. while (foundPos != -1) {
  836. if (foundPos === 0 && elem.length != entry.length) {
  837. ret.push('<li><strong>' + elem.substr(0, entry.length) + '</strong>' +
  838. elem.substr(entry.length) + '</li>');
  839. break;
  840. } else if (entry.length >= instance.options.partialChars &&
  841. instance.options.partialSearch && foundPos != -1) {
  842. if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos - 1, 1))) {
  843. partial.push('<li>' + elem.substr(0, foundPos) + '<strong>' +
  844. elem.substr(foundPos, entry.length) + '</strong>' + elem.substr(
  845. foundPos + entry.length) + '</li>');
  846. break;
  847. }
  848. }
  849. foundPos = instance.options.ignoreCase ?
  850. elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
  851. elem.indexOf(entry, foundPos + 1);
  852. }
  853. }
  854. if (partial.length) {
  855. ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
  856. }
  857. return '<ul>' + ret.join('') + '</ul>';
  858. }
  859. }, options || {});
  860. }
  861. });
  862. /***
  863. AJAX in-place editor
  864. see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor
  865. Use this if you notice weird scrolling problems on some browsers,
  866. the DOM might be a bit confused when this gets called so do this
  867. waits 1 ms (with setTimeout) until it does the activation
  868. ***/
  869. /** @id Ajax.InPlaceEditor */
  870. Ajax.InPlaceEditor = function (element, url, options) {
  871. this.__init__(element, url, options);
  872. };
  873. /** @id Ajax.InPlaceEditor.defaultHighlightColor */
  874. Ajax.InPlaceEditor.defaultHighlightColor = '#FFFF99';
  875. Ajax.InPlaceEditor.prototype = {
  876. __init__: function (element, url, options) {
  877. this.url = url;
  878. this.element = MochiKit.DOM.getElement(element);
  879. this.options = MochiKit.Base.update({
  880. okButton: true,
  881. okText: 'ok',
  882. cancelLink: true,
  883. cancelText: 'cancel',
  884. savingText: 'Saving...',
  885. clickToEditText: 'Click to edit',
  886. okText: 'ok',
  887. rows: 1,
  888. onComplete: function (transport, element) {
  889. new MochiKit.Visual.Highlight(element, {startcolor: this.options.highlightcolor});
  890. },
  891. onFailure: function (transport) {
  892. alert('Error communicating with the server: ' + MochiKit.Base.stripTags(transport.responseText));
  893. },
  894. callback: function (form) {
  895. return MochiKit.DOM.formContents(form);
  896. },
  897. handleLineBreaks: true,
  898. loadingText: 'Loading...',
  899. savingClassName: 'inplaceeditor-saving',
  900. loadingClassName: 'inplaceeditor-loading',
  901. formClassName: 'inplaceeditor-form',
  902. highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
  903. highlightendcolor: '#FFFFFF',
  904. externalControl: null,
  905. submitOnBlur: false,
  906. ajaxOptions: {}
  907. }, options || {});
  908. if (!this.options.formId && this.element.id) {
  909. this.options.formId = this.element.id + '-inplaceeditor';
  910. if (MochiKit.DOM.getElement(this.options.formId)) {
  911. // there's already a form with that name, don't specify an id
  912. this.options.formId = null;
  913. }
  914. }
  915. if (this.options.externalControl) {
  916. this.options.externalControl = MochiKit.DOM.getElement(this.options.externalControl);
  917. }
  918. this.originalBackground = MochiKit.Style.getStyle(this.element, 'background-color');
  919. if (!this.originalBackground) {
  920. this.originalBackground = 'transparent';
  921. }
  922. this.element.title = this.options.clickToEditText;
  923. this.onclickListener = MochiKit.Signal.connect(this.element, 'onclick', this, this.enterEditMode);
  924. this.mouseoverListener = MochiKit.Signal.connect(this.element, 'onmouseover', this, this.enterHover);
  925. this.mouseoutListener = MochiKit.Signal.connect(this.element, 'onmouseout', this, this.leaveHover);
  926. if (this.options.externalControl) {
  927. this.onclickListenerExternal = MochiKit.Signal.connect(this.options.externalControl,
  928. 'onclick', this, this.enterEditMode);
  929. this.mouseoverListenerExternal = MochiKit.Signal.connect(this.options.externalControl,
  930. 'onmouseover', this, this.enterHover);
  931. this.mouseoutListenerExternal = MochiKit.Signal.connect(this.options.externalControl,
  932. 'onmouseout', this, this.leaveHover);
  933. }
  934. },
  935. /** @id Ajax.InPlaceEditor.prototype.enterEditMode */
  936. enterEditMode: function (evt) {
  937. if (this.saving) {
  938. return;
  939. }
  940. if (this.editing) {
  941. return;
  942. }
  943. this.editing = true;
  944. this.onEnterEditMode();
  945. if (this.options.externalControl) {
  946. MochiKit.Style.hideElement(this.options.externalControl);
  947. }
  948. MochiKit.Style.hideElement(this.element);
  949. this.createForm();
  950. this.element.parentNode.insertBefore(this.form, this.element);
  951. Field.scrollFreeActivate(this.editField);
  952. // stop the event to avoid a page refresh in Safari
  953. if (evt) {
  954. evt.stop();
  955. }
  956. return false;
  957. },
  958. /** @id Ajax.InPlaceEditor.prototype.createForm */
  959. createForm: function () {
  960. this.form = document.createElement('form');
  961. this.form.id = this.options.formId;
  962. MochiKit.DOM.addElementClass(this.form, this.options.formClassName)
  963. this.form.onsubmit = MochiKit.Base.bind(this.onSubmit, this);
  964. this.createEditField();
  965. if (this.options.textarea) {
  966. var br = document.createElement('br');
  967. this.form.appendChild(br);
  968. }
  969. if (this.options.okButton) {
  970. okButton = document.createElement('input');
  971. okButton.type = 'submit';
  972. okButton.value = this.options.okText;
  973. this.form.appendChild(okButton);
  974. }
  975. if (this.options.cancelLink) {
  976. cancelLink = document.createElement('a');
  977. cancelLink.href = '#';
  978. cancelLink.appendChild(document.createTextNode(this.options.cancelText));
  979. cancelLink.onclick = MochiKit.Base.bind(this.onclickCancel, this);
  980. this.form.appendChild(cancelLink);
  981. }
  982. },
  983. /** @id Ajax.InPlaceEditor.prototype.hasHTMLLineBreaks */
  984. hasHTMLLineBreaks: function (string) {
  985. if (!this.options.handleLineBreaks) {
  986. return false;
  987. }
  988. return string.match(/<br/i) || string.match(/<p>/i);
  989. },
  990. /** @id Ajax.InPlaceEditor.prototype.convertHTMLLineBreaks */
  991. convertHTMLLineBreaks: function (string) {
  992. return string.replace(/<br>/gi, '\n').replace(/<br\/>/gi, '\n').replace(/<\/p>/gi, '\n').replace(/<p>/gi, '');
  993. },
  994. /** @id Ajax.InPlaceEditor.prototype.createEditField */
  995. createEditField: function () {
  996. var text;
  997. if (this.options.loadTextURL) {
  998. text = this.options.loadingText;
  999. } else {
  1000. text = this.getText();
  1001. }
  1002. var obj = this;
  1003. if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) {
  1004. this.options.textarea = false;
  1005. var textField = document.createElement('input');
  1006. textField.obj = this;
  1007. textField.type = 'text';
  1008. textField.name = 'value';
  1009. textField.value = text;
  1010. textField.style.backgroundColor = this.options.highlightcolor;
  1011. var size = this.options.size || this.options.cols || 0;
  1012. if (size !== 0) {
  1013. textField.size = size;
  1014. }
  1015. if (this.options.submitOnBlur) {
  1016. textField.onblur = MochiKit.Base.bind(this.onSubmit, this);
  1017. }
  1018. this.editField = textField;
  1019. } else {
  1020. this.options.textarea = true;
  1021. var textArea = document.createElement('textarea');
  1022. textArea.obj = this;
  1023. textArea.name = 'value';
  1024. textArea.value = this.convertHTMLLineBreaks(text);
  1025. textArea.rows = this.options.rows;
  1026. textArea.cols = this.options.cols || 40;
  1027. if (this.options.submitOnBlur) {
  1028. textArea.onblur = MochiKit.Base.bind(this.onSubmit, this);
  1029. }
  1030. this.editField = textArea;
  1031. }
  1032. if (this.options.loadTextURL) {
  1033. this.loadExternalText();
  1034. }
  1035. this.form.appendChild(this.editField);
  1036. },
  1037. /** @id Ajax.InPlaceEditor.prototype.getText */
  1038. getText: function () {
  1039. return this.element.innerHTML;
  1040. },
  1041. /** @id Ajax.InPlaceEditor.prototype.loadExternalText */
  1042. loadExternalText: function () {
  1043. MochiKit.DOM.addElementClass(this.form, this.options.loadingClassName);
  1044. this.editField.disabled = true;
  1045. new Ajax.Request(
  1046. this.options.loadTextURL,
  1047. MochiKit.Base.update({
  1048. asynchronous: true,
  1049. onComplete: MochiKit.Base.bind(this.onLoadedExternalText, this)
  1050. }, this.options.ajaxOptions)
  1051. );
  1052. },
  1053. /** @id Ajax.InPlaceEditor.prototype.onLoadedExternalText */
  1054. onLoadedExternalText: function (transport) {
  1055. MochiKit.DOM.removeElementClass(this.form, this.options.loadingClassName);
  1056. this.editField.disabled = false;
  1057. this.editField.value = MochiKit.Base.stripTags(transport);
  1058. },
  1059. /** @id Ajax.InPlaceEditor.prototype.onclickCancel */
  1060. onclickCancel: function () {
  1061. this.onComplete();
  1062. this.leaveEditMode();
  1063. return false;
  1064. },
  1065. /** @id Ajax.InPlaceEditor.prototype.onFailure */
  1066. onFailure: function (transport) {
  1067. this.options.onFailure(transport);
  1068. if (this.oldInnerHTML) {
  1069. this.element.innerHTML = this.oldInnerHTML;
  1070. this.oldInnerHTML = null;
  1071. }
  1072. return false;
  1073. },
  1074. /** @id Ajax.InPlaceEditor.prototype.onSubmit */
  1075. onSubmit: function () {
  1076. // onLoading resets these so we need to save them away for the Ajax call
  1077. var form = this.form;
  1078. var value = this.editField.value;
  1079. // do this first, sometimes the ajax call returns before we get a
  1080. // chance to switch on Saving which means this will actually switch on
  1081. // Saving *after* we have left edit mode causing Saving to be
  1082. // displayed indefinitely
  1083. this.onLoading();
  1084. new Ajax.Updater(
  1085. {
  1086. success: this.element,
  1087. // dont update on failure (this could be an option)
  1088. failure: null
  1089. },
  1090. this.url,
  1091. MochiKit.Base.update({
  1092. parameters: this.options.callback(form, value),
  1093. onComplete: MochiKit.Base.bind(this.onComplete, this),
  1094. onFailure: MochiKit.Base.bind(this.onFailure, this)
  1095. }, this.options.ajaxOptions)
  1096. );
  1097. // stop the event to avoid a page refresh in Safari
  1098. if (arguments.length > 1) {
  1099. arguments[0].stop();
  1100. }
  1101. return false;
  1102. },
  1103. /** @id Ajax.InPlaceEditor.prototype.onLoading */
  1104. onLoading: function () {
  1105. this.saving = true;
  1106. this.removeForm();
  1107. this.leaveHover();
  1108. this.showSaving();
  1109. },
  1110. /** @id Ajax.InPlaceEditor.prototype.onSaving */
  1111. showSaving: function () {
  1112. this.oldInnerHTML = this.element.innerHTML;
  1113. this.element.innerHTML = this.options.savingText;
  1114. MochiKit.DOM.addElementClass(this.element, this.options.savingClassName);
  1115. this.element.style.backgroundColor = this.originalBackground;
  1116. MochiKit.Style.showElement(this.element);
  1117. },
  1118. /** @id Ajax.InPlaceEditor.prototype.removeForm */
  1119. removeForm: function () {
  1120. if (this.form) {
  1121. if (this.form.parentNode) {
  1122. MochiKit.DOM.removeElement(this.form);
  1123. }
  1124. this.form = null;
  1125. }
  1126. },
  1127. /** @id Ajax.InPlaceEditor.prototype.enterHover */
  1128. enterHover: function () {
  1129. if (this.saving) {
  1130. return;
  1131. }
  1132. this.element.style.backgroundColor = this.options.highlightcolor;
  1133. if (this.effect) {
  1134. this.effect.cancel();
  1135. }
  1136. MochiKit.DOM.addElementClass(this.element, this.options.hoverClassName)
  1137. },
  1138. /** @id Ajax.InPlaceEditor.prototype.leaveHover */
  1139. leaveHover: function () {
  1140. if (this.options.backgroundColor) {
  1141. this.element.style.backgroundColor = this.oldBackground;
  1142. }
  1143. MochiKit.DOM.removeElementClass(this.element, this.options.hoverClassName)
  1144. if (this.saving) {
  1145. return;
  1146. }
  1147. this.effect = new MochiKit.Visual.Highlight(this.element, {
  1148. startcolor: this.options.highlightcolor,
  1149. endcolor: this.options.highlightendcolor,
  1150. restorecolor: this.originalBackground
  1151. });
  1152. },
  1153. /** @id Ajax.InPlaceEditor.prototype.leaveEditMode */
  1154. leaveEditMode: function () {
  1155. MochiKit.DOM.removeElementClass(this.element, this.options.savingClassName);
  1156. this.removeForm();
  1157. this.leaveHover();
  1158. this.element.style.backgroundColor = this.originalBackground;
  1159. MochiKit.Style.showElement(this.element);
  1160. if (this.options.externalControl) {
  1161. MochiKit.Style.showElement(this.options.externalControl);
  1162. }
  1163. this.editing = false;
  1164. this.saving = false;
  1165. this.oldInnerHTML = null;
  1166. this.onLeaveEditMode();
  1167. },
  1168. /** @id Ajax.InPlaceEditor.prototype.onComplete */
  1169. onComplete: function (transport) {
  1170. this.leaveEditMode();
  1171. MochiKit.Base.bind(this.options.onComplete, this)(transport, this.element);
  1172. },
  1173. /** @id Ajax.InPlaceEditor.prototype.onEnterEditMode */
  1174. onEnterEditMode: function () {},
  1175. /** @id Ajax.InPlaceEditor.prototype.onLeaveEditMode */
  1176. onLeaveEditMode: function () {},
  1177. /** @id Ajax.InPlaceEditor.prototype.dispose */
  1178. dispose: function () {
  1179. if (this.oldInnerHTML) {
  1180. this.element.innerHTML = this.oldInnerHTML;
  1181. }
  1182. this.leaveEditMode();
  1183. MochiKit.Signal.disconnect(this.onclickListener);
  1184. MochiKit.Signal.disconnect(this.mouseoverListener);
  1185. MochiKit.Signal.disconnect(this.mouseoutListener);
  1186. if (this.options.externalControl) {
  1187. MochiKit.Signal.disconnect(this.onclickListenerExternal);
  1188. MochiKit.Signal.disconnect(this.mouseoverListenerExternal);
  1189. MochiKit.Signal.disconnect(this.mouseoutListenerExternal);
  1190. }
  1191. }
  1192. };