testing/mochitest/MochiKit/Controls.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial