1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/tests/mochitest/ajax/scriptaculous/src/controls.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,875 @@ 1.4 +// script.aculo.us controls.js v1.7.1_beta2, Tue May 15 15:15:45 EDT 2007 1.5 + 1.6 +// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) 1.7 +// (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan) 1.8 +// (c) 2005-2007 Jon Tirsen (http://www.tirsen.com) 1.9 +// Contributors: 1.10 +// Richard Livsey 1.11 +// Rahul Bhargava 1.12 +// Rob Wills 1.13 +// 1.14 +// script.aculo.us is freely distributable under the terms of an MIT-style license. 1.15 +// For details, see the script.aculo.us web site: http://script.aculo.us/ 1.16 + 1.17 +// Autocompleter.Base handles all the autocompletion functionality 1.18 +// that's independent of the data source for autocompletion. This 1.19 +// includes drawing the autocompletion menu, observing keyboard 1.20 +// and mouse events, and similar. 1.21 +// 1.22 +// Specific autocompleters need to provide, at the very least, 1.23 +// a getUpdatedChoices function that will be invoked every time 1.24 +// the text inside the monitored textbox changes. This method 1.25 +// should get the text for which to provide autocompletion by 1.26 +// invoking this.getToken(), NOT by directly accessing 1.27 +// this.element.value. This is to allow incremental tokenized 1.28 +// autocompletion. Specific auto-completion logic (AJAX, etc) 1.29 +// belongs in getUpdatedChoices. 1.30 +// 1.31 +// Tokenized incremental autocompletion is enabled automatically 1.32 +// when an autocompleter is instantiated with the 'tokens' option 1.33 +// in the options parameter, e.g.: 1.34 +// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); 1.35 +// will incrementally autocomplete with a comma as the token. 1.36 +// Additionally, ',' in the above example can be replaced with 1.37 +// a token array, e.g. { tokens: [',', '\n'] } which 1.38 +// enables autocompletion on multiple tokens. This is most 1.39 +// useful when one of the tokens is \n (a newline), as it 1.40 +// allows smart autocompletion after linebreaks. 1.41 + 1.42 +if(typeof Effect == 'undefined') 1.43 + throw("controls.js requires including script.aculo.us' effects.js library"); 1.44 + 1.45 +var Autocompleter = {} 1.46 +Autocompleter.Base = function() {}; 1.47 +Autocompleter.Base.prototype = { 1.48 + baseInitialize: function(element, update, options) { 1.49 + element = $(element) 1.50 + this.element = element; 1.51 + this.update = $(update); 1.52 + this.hasFocus = false; 1.53 + this.changed = false; 1.54 + this.active = false; 1.55 + this.index = 0; 1.56 + this.entryCount = 0; 1.57 + 1.58 + if(this.setOptions) 1.59 + this.setOptions(options); 1.60 + else 1.61 + this.options = options || {}; 1.62 + 1.63 + this.options.paramName = this.options.paramName || this.element.name; 1.64 + this.options.tokens = this.options.tokens || []; 1.65 + this.options.frequency = this.options.frequency || 0.4; 1.66 + this.options.minChars = this.options.minChars || 1; 1.67 + this.options.onShow = this.options.onShow || 1.68 + function(element, update){ 1.69 + if(!update.style.position || update.style.position=='absolute') { 1.70 + update.style.position = 'absolute'; 1.71 + Position.clone(element, update, { 1.72 + setHeight: false, 1.73 + offsetTop: element.offsetHeight 1.74 + }); 1.75 + } 1.76 + Effect.Appear(update,{duration:0.15}); 1.77 + }; 1.78 + this.options.onHide = this.options.onHide || 1.79 + function(element, update){ new Effect.Fade(update,{duration:0.15}) }; 1.80 + 1.81 + if(typeof(this.options.tokens) == 'string') 1.82 + this.options.tokens = new Array(this.options.tokens); 1.83 + 1.84 + this.observer = null; 1.85 + 1.86 + this.element.setAttribute('autocomplete','off'); 1.87 + 1.88 + Element.hide(this.update); 1.89 + 1.90 + Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this)); 1.91 + Event.observe(this.element, 'keypress', this.onKeyPress.bindAsEventListener(this)); 1.92 + 1.93 + // Turn autocomplete back on when the user leaves the page, so that the 1.94 + // field's value will be remembered on Mozilla-based browsers. 1.95 + Event.observe(window, 'beforeunload', function(){ 1.96 + element.setAttribute('autocomplete', 'on'); 1.97 + }); 1.98 + }, 1.99 + 1.100 + show: function() { 1.101 + if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); 1.102 + if(!this.iefix && 1.103 + (Prototype.Browser.IE) && 1.104 + (Element.getStyle(this.update, 'position')=='absolute')) { 1.105 + new Insertion.After(this.update, 1.106 + '<iframe id="' + this.update.id + '_iefix" '+ 1.107 + 'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' + 1.108 + 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>'); 1.109 + this.iefix = $(this.update.id+'_iefix'); 1.110 + } 1.111 + if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50); 1.112 + }, 1.113 + 1.114 + fixIEOverlapping: function() { 1.115 + Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)}); 1.116 + this.iefix.style.zIndex = 1; 1.117 + this.update.style.zIndex = 2; 1.118 + Element.show(this.iefix); 1.119 + }, 1.120 + 1.121 + hide: function() { 1.122 + this.stopIndicator(); 1.123 + if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); 1.124 + if(this.iefix) Element.hide(this.iefix); 1.125 + }, 1.126 + 1.127 + startIndicator: function() { 1.128 + if(this.options.indicator) Element.show(this.options.indicator); 1.129 + }, 1.130 + 1.131 + stopIndicator: function() { 1.132 + if(this.options.indicator) Element.hide(this.options.indicator); 1.133 + }, 1.134 + 1.135 + onKeyPress: function(event) { 1.136 + if(this.active) 1.137 + switch(event.keyCode) { 1.138 + case Event.KEY_TAB: 1.139 + case Event.KEY_RETURN: 1.140 + this.selectEntry(); 1.141 + Event.stop(event); 1.142 + case Event.KEY_ESC: 1.143 + this.hide(); 1.144 + this.active = false; 1.145 + Event.stop(event); 1.146 + return; 1.147 + case Event.KEY_LEFT: 1.148 + case Event.KEY_RIGHT: 1.149 + return; 1.150 + case Event.KEY_UP: 1.151 + this.markPrevious(); 1.152 + this.render(); 1.153 + if(Prototype.Browser.WebKit) Event.stop(event); 1.154 + return; 1.155 + case Event.KEY_DOWN: 1.156 + this.markNext(); 1.157 + this.render(); 1.158 + if(Prototype.Browser.WebKit) Event.stop(event); 1.159 + return; 1.160 + } 1.161 + else 1.162 + if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || 1.163 + (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return; 1.164 + 1.165 + this.changed = true; 1.166 + this.hasFocus = true; 1.167 + 1.168 + if(this.observer) clearTimeout(this.observer); 1.169 + this.observer = 1.170 + setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); 1.171 + }, 1.172 + 1.173 + activate: function() { 1.174 + this.changed = false; 1.175 + this.hasFocus = true; 1.176 + this.getUpdatedChoices(); 1.177 + }, 1.178 + 1.179 + onHover: function(event) { 1.180 + var element = Event.findElement(event, 'LI'); 1.181 + if(this.index != element.autocompleteIndex) 1.182 + { 1.183 + this.index = element.autocompleteIndex; 1.184 + this.render(); 1.185 + } 1.186 + Event.stop(event); 1.187 + }, 1.188 + 1.189 + onClick: function(event) { 1.190 + var element = Event.findElement(event, 'LI'); 1.191 + this.index = element.autocompleteIndex; 1.192 + this.selectEntry(); 1.193 + this.hide(); 1.194 + }, 1.195 + 1.196 + onBlur: function(event) { 1.197 + // needed to make click events working 1.198 + setTimeout(this.hide.bind(this), 250); 1.199 + this.hasFocus = false; 1.200 + this.active = false; 1.201 + }, 1.202 + 1.203 + render: function() { 1.204 + if(this.entryCount > 0) { 1.205 + for (var i = 0; i < this.entryCount; i++) 1.206 + this.index==i ? 1.207 + Element.addClassName(this.getEntry(i),"selected") : 1.208 + Element.removeClassName(this.getEntry(i),"selected"); 1.209 + if(this.hasFocus) { 1.210 + this.show(); 1.211 + this.active = true; 1.212 + } 1.213 + } else { 1.214 + this.active = false; 1.215 + this.hide(); 1.216 + } 1.217 + }, 1.218 + 1.219 + markPrevious: function() { 1.220 + if(this.index > 0) this.index-- 1.221 + else this.index = this.entryCount-1; 1.222 + this.getEntry(this.index).scrollIntoView(true); 1.223 + }, 1.224 + 1.225 + markNext: function() { 1.226 + if(this.index < this.entryCount-1) this.index++ 1.227 + else this.index = 0; 1.228 + this.getEntry(this.index).scrollIntoView(false); 1.229 + }, 1.230 + 1.231 + getEntry: function(index) { 1.232 + return this.update.firstChild.childNodes[index]; 1.233 + }, 1.234 + 1.235 + getCurrentEntry: function() { 1.236 + return this.getEntry(this.index); 1.237 + }, 1.238 + 1.239 + selectEntry: function() { 1.240 + this.active = false; 1.241 + this.updateElement(this.getCurrentEntry()); 1.242 + }, 1.243 + 1.244 + updateElement: function(selectedElement) { 1.245 + if (this.options.updateElement) { 1.246 + this.options.updateElement(selectedElement); 1.247 + return; 1.248 + } 1.249 + var value = ''; 1.250 + if (this.options.select) { 1.251 + var nodes = document.getElementsByClassName(this.options.select, selectedElement) || []; 1.252 + if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); 1.253 + } else 1.254 + value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); 1.255 + 1.256 + var lastTokenPos = this.findLastToken(); 1.257 + if (lastTokenPos != -1) { 1.258 + var newValue = this.element.value.substr(0, lastTokenPos + 1); 1.259 + var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/); 1.260 + if (whitespace) 1.261 + newValue += whitespace[0]; 1.262 + this.element.value = newValue + value; 1.263 + } else { 1.264 + this.element.value = value; 1.265 + } 1.266 + this.element.focus(); 1.267 + 1.268 + if (this.options.afterUpdateElement) 1.269 + this.options.afterUpdateElement(this.element, selectedElement); 1.270 + }, 1.271 + 1.272 + updateChoices: function(choices) { 1.273 + if(!this.changed && this.hasFocus) { 1.274 + this.update.innerHTML = choices; 1.275 + Element.cleanWhitespace(this.update); 1.276 + Element.cleanWhitespace(this.update.down()); 1.277 + 1.278 + if(this.update.firstChild && this.update.down().childNodes) { 1.279 + this.entryCount = 1.280 + this.update.down().childNodes.length; 1.281 + for (var i = 0; i < this.entryCount; i++) { 1.282 + var entry = this.getEntry(i); 1.283 + entry.autocompleteIndex = i; 1.284 + this.addObservers(entry); 1.285 + } 1.286 + } else { 1.287 + this.entryCount = 0; 1.288 + } 1.289 + 1.290 + this.stopIndicator(); 1.291 + this.index = 0; 1.292 + 1.293 + if(this.entryCount==1 && this.options.autoSelect) { 1.294 + this.selectEntry(); 1.295 + this.hide(); 1.296 + } else { 1.297 + this.render(); 1.298 + } 1.299 + } 1.300 + }, 1.301 + 1.302 + addObservers: function(element) { 1.303 + Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); 1.304 + Event.observe(element, "click", this.onClick.bindAsEventListener(this)); 1.305 + }, 1.306 + 1.307 + onObserverEvent: function() { 1.308 + this.changed = false; 1.309 + if(this.getToken().length>=this.options.minChars) { 1.310 + this.getUpdatedChoices(); 1.311 + } else { 1.312 + this.active = false; 1.313 + this.hide(); 1.314 + } 1.315 + }, 1.316 + 1.317 + getToken: function() { 1.318 + var tokenPos = this.findLastToken(); 1.319 + if (tokenPos != -1) 1.320 + var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,''); 1.321 + else 1.322 + var ret = this.element.value; 1.323 + 1.324 + return /\n/.test(ret) ? '' : ret; 1.325 + }, 1.326 + 1.327 + findLastToken: function() { 1.328 + var lastTokenPos = -1; 1.329 + 1.330 + for (var i=0; i<this.options.tokens.length; i++) { 1.331 + var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]); 1.332 + if (thisTokenPos > lastTokenPos) 1.333 + lastTokenPos = thisTokenPos; 1.334 + } 1.335 + return lastTokenPos; 1.336 + } 1.337 +} 1.338 + 1.339 +Ajax.Autocompleter = Class.create(); 1.340 +Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), { 1.341 + initialize: function(element, update, url, options) { 1.342 + this.baseInitialize(element, update, options); 1.343 + this.options.asynchronous = true; 1.344 + this.options.onComplete = this.onComplete.bind(this); 1.345 + this.options.defaultParams = this.options.parameters || null; 1.346 + this.url = url; 1.347 + }, 1.348 + 1.349 + getUpdatedChoices: function() { 1.350 + this.startIndicator(); 1.351 + 1.352 + var entry = encodeURIComponent(this.options.paramName) + '=' + 1.353 + encodeURIComponent(this.getToken()); 1.354 + 1.355 + this.options.parameters = this.options.callback ? 1.356 + this.options.callback(this.element, entry) : entry; 1.357 + 1.358 + if(this.options.defaultParams) 1.359 + this.options.parameters += '&' + this.options.defaultParams; 1.360 + 1.361 + new Ajax.Request(this.url, this.options); 1.362 + }, 1.363 + 1.364 + onComplete: function(request) { 1.365 + this.updateChoices(request.responseText); 1.366 + } 1.367 + 1.368 +}); 1.369 + 1.370 +// The local array autocompleter. Used when you'd prefer to 1.371 +// inject an array of autocompletion options into the page, rather 1.372 +// than sending out Ajax queries, which can be quite slow sometimes. 1.373 +// 1.374 +// The constructor takes four parameters. The first two are, as usual, 1.375 +// the id of the monitored textbox, and id of the autocompletion menu. 1.376 +// The third is the array you want to autocomplete from, and the fourth 1.377 +// is the options block. 1.378 +// 1.379 +// Extra local autocompletion options: 1.380 +// - choices - How many autocompletion choices to offer 1.381 +// 1.382 +// - partialSearch - If false, the autocompleter will match entered 1.383 +// text only at the beginning of strings in the 1.384 +// autocomplete array. Defaults to true, which will 1.385 +// match text at the beginning of any *word* in the 1.386 +// strings in the autocomplete array. If you want to 1.387 +// search anywhere in the string, additionally set 1.388 +// the option fullSearch to true (default: off). 1.389 +// 1.390 +// - fullSsearch - Search anywhere in autocomplete array strings. 1.391 +// 1.392 +// - partialChars - How many characters to enter before triggering 1.393 +// a partial match (unlike minChars, which defines 1.394 +// how many characters are required to do any match 1.395 +// at all). Defaults to 2. 1.396 +// 1.397 +// - ignoreCase - Whether to ignore case when autocompleting. 1.398 +// Defaults to true. 1.399 +// 1.400 +// It's possible to pass in a custom function as the 'selector' 1.401 +// option, if you prefer to write your own autocompletion logic. 1.402 +// In that case, the other options above will not apply unless 1.403 +// you support them. 1.404 + 1.405 +Autocompleter.Local = Class.create(); 1.406 +Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), { 1.407 + initialize: function(element, update, array, options) { 1.408 + this.baseInitialize(element, update, options); 1.409 + this.options.array = array; 1.410 + }, 1.411 + 1.412 + getUpdatedChoices: function() { 1.413 + this.updateChoices(this.options.selector(this)); 1.414 + }, 1.415 + 1.416 + setOptions: function(options) { 1.417 + this.options = Object.extend({ 1.418 + choices: 10, 1.419 + partialSearch: true, 1.420 + partialChars: 2, 1.421 + ignoreCase: true, 1.422 + fullSearch: false, 1.423 + selector: function(instance) { 1.424 + var ret = []; // Beginning matches 1.425 + var partial = []; // Inside matches 1.426 + var entry = instance.getToken(); 1.427 + var count = 0; 1.428 + 1.429 + for (var i = 0; i < instance.options.array.length && 1.430 + ret.length < instance.options.choices ; i++) { 1.431 + 1.432 + var elem = instance.options.array[i]; 1.433 + var foundPos = instance.options.ignoreCase ? 1.434 + elem.toLowerCase().indexOf(entry.toLowerCase()) : 1.435 + elem.indexOf(entry); 1.436 + 1.437 + while (foundPos != -1) { 1.438 + if (foundPos == 0 && elem.length != entry.length) { 1.439 + ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" + 1.440 + elem.substr(entry.length) + "</li>"); 1.441 + break; 1.442 + } else if (entry.length >= instance.options.partialChars && 1.443 + instance.options.partialSearch && foundPos != -1) { 1.444 + if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { 1.445 + partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" + 1.446 + elem.substr(foundPos, entry.length) + "</strong>" + elem.substr( 1.447 + foundPos + entry.length) + "</li>"); 1.448 + break; 1.449 + } 1.450 + } 1.451 + 1.452 + foundPos = instance.options.ignoreCase ? 1.453 + elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : 1.454 + elem.indexOf(entry, foundPos + 1); 1.455 + 1.456 + } 1.457 + } 1.458 + if (partial.length) 1.459 + ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)) 1.460 + return "<ul>" + ret.join('') + "</ul>"; 1.461 + } 1.462 + }, options || {}); 1.463 + } 1.464 +}); 1.465 + 1.466 +// AJAX in-place editor 1.467 +// 1.468 +// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor 1.469 + 1.470 +// Use this if you notice weird scrolling problems on some browsers, 1.471 +// the DOM might be a bit confused when this gets called so do this 1.472 +// waits 1 ms (with setTimeout) until it does the activation 1.473 +Field.scrollFreeActivate = function(field) { 1.474 + setTimeout(function() { 1.475 + Field.activate(field); 1.476 + }, 1); 1.477 +} 1.478 + 1.479 +Ajax.InPlaceEditor = Class.create(); 1.480 +Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99"; 1.481 +Ajax.InPlaceEditor.prototype = { 1.482 + initialize: function(element, url, options) { 1.483 + this.url = url; 1.484 + this.element = $(element); 1.485 + 1.486 + this.options = Object.extend({ 1.487 + paramName: "value", 1.488 + okButton: true, 1.489 + okLink: false, 1.490 + okText: "ok", 1.491 + cancelButton: false, 1.492 + cancelLink: true, 1.493 + cancelText: "cancel", 1.494 + textBeforeControls: '', 1.495 + textBetweenControls: '', 1.496 + textAfterControls: '', 1.497 + savingText: "Saving...", 1.498 + clickToEditText: "Click to edit", 1.499 + okText: "ok", 1.500 + rows: 1, 1.501 + onComplete: function(transport, element) { 1.502 + new Effect.Highlight(element, {startcolor: this.options.highlightcolor}); 1.503 + }, 1.504 + onFailure: function(transport) { 1.505 + alert("Error communicating with the server: " + transport.responseText.stripTags()); 1.506 + }, 1.507 + callback: function(form) { 1.508 + return Form.serialize(form); 1.509 + }, 1.510 + handleLineBreaks: true, 1.511 + loadingText: 'Loading...', 1.512 + savingClassName: 'inplaceeditor-saving', 1.513 + loadingClassName: 'inplaceeditor-loading', 1.514 + formClassName: 'inplaceeditor-form', 1.515 + highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor, 1.516 + highlightendcolor: "#FFFFFF", 1.517 + externalControl: null, 1.518 + submitOnBlur: false, 1.519 + ajaxOptions: {}, 1.520 + evalScripts: false 1.521 + }, options || {}); 1.522 + 1.523 + if(!this.options.formId && this.element.id) { 1.524 + this.options.formId = this.element.id + "-inplaceeditor"; 1.525 + if ($(this.options.formId)) { 1.526 + // there's already a form with that name, don't specify an id 1.527 + this.options.formId = null; 1.528 + } 1.529 + } 1.530 + 1.531 + if (this.options.externalControl) { 1.532 + this.options.externalControl = $(this.options.externalControl); 1.533 + } 1.534 + 1.535 + this.originalBackground = Element.getStyle(this.element, 'background-color'); 1.536 + if (!this.originalBackground) { 1.537 + this.originalBackground = "transparent"; 1.538 + } 1.539 + 1.540 + this.element.title = this.options.clickToEditText; 1.541 + 1.542 + this.onclickListener = this.enterEditMode.bindAsEventListener(this); 1.543 + this.mouseoverListener = this.enterHover.bindAsEventListener(this); 1.544 + this.mouseoutListener = this.leaveHover.bindAsEventListener(this); 1.545 + Event.observe(this.element, 'click', this.onclickListener); 1.546 + Event.observe(this.element, 'mouseover', this.mouseoverListener); 1.547 + Event.observe(this.element, 'mouseout', this.mouseoutListener); 1.548 + if (this.options.externalControl) { 1.549 + Event.observe(this.options.externalControl, 'click', this.onclickListener); 1.550 + Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener); 1.551 + Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener); 1.552 + } 1.553 + }, 1.554 + enterEditMode: function(evt) { 1.555 + if (this.saving) return; 1.556 + if (this.editing) return; 1.557 + this.editing = true; 1.558 + this.onEnterEditMode(); 1.559 + if (this.options.externalControl) { 1.560 + Element.hide(this.options.externalControl); 1.561 + } 1.562 + Element.hide(this.element); 1.563 + this.createForm(); 1.564 + this.element.parentNode.insertBefore(this.form, this.element); 1.565 + if (!this.options.loadTextURL) Field.scrollFreeActivate(this.editField); 1.566 + // stop the event to avoid a page refresh in Safari 1.567 + if (evt) { 1.568 + Event.stop(evt); 1.569 + } 1.570 + return false; 1.571 + }, 1.572 + createForm: function() { 1.573 + this.form = document.createElement("form"); 1.574 + this.form.id = this.options.formId; 1.575 + Element.addClassName(this.form, this.options.formClassName) 1.576 + this.form.onsubmit = this.onSubmit.bind(this); 1.577 + 1.578 + this.createEditField(); 1.579 + 1.580 + if (this.options.textarea) { 1.581 + var br = document.createElement("br"); 1.582 + this.form.appendChild(br); 1.583 + } 1.584 + 1.585 + if (this.options.textBeforeControls) 1.586 + this.form.appendChild(document.createTextNode(this.options.textBeforeControls)); 1.587 + 1.588 + if (this.options.okButton) { 1.589 + var okButton = document.createElement("input"); 1.590 + okButton.type = "submit"; 1.591 + okButton.value = this.options.okText; 1.592 + okButton.className = 'editor_ok_button'; 1.593 + this.form.appendChild(okButton); 1.594 + } 1.595 + 1.596 + if (this.options.okLink) { 1.597 + var okLink = document.createElement("a"); 1.598 + okLink.href = "#"; 1.599 + okLink.appendChild(document.createTextNode(this.options.okText)); 1.600 + okLink.onclick = this.onSubmit.bind(this); 1.601 + okLink.className = 'editor_ok_link'; 1.602 + this.form.appendChild(okLink); 1.603 + } 1.604 + 1.605 + if (this.options.textBetweenControls && 1.606 + (this.options.okLink || this.options.okButton) && 1.607 + (this.options.cancelLink || this.options.cancelButton)) 1.608 + this.form.appendChild(document.createTextNode(this.options.textBetweenControls)); 1.609 + 1.610 + if (this.options.cancelButton) { 1.611 + var cancelButton = document.createElement("input"); 1.612 + cancelButton.type = "submit"; 1.613 + cancelButton.value = this.options.cancelText; 1.614 + cancelButton.onclick = this.onclickCancel.bind(this); 1.615 + cancelButton.className = 'editor_cancel_button'; 1.616 + this.form.appendChild(cancelButton); 1.617 + } 1.618 + 1.619 + if (this.options.cancelLink) { 1.620 + var cancelLink = document.createElement("a"); 1.621 + cancelLink.href = "#"; 1.622 + cancelLink.appendChild(document.createTextNode(this.options.cancelText)); 1.623 + cancelLink.onclick = this.onclickCancel.bind(this); 1.624 + cancelLink.className = 'editor_cancel editor_cancel_link'; 1.625 + this.form.appendChild(cancelLink); 1.626 + } 1.627 + 1.628 + if (this.options.textAfterControls) 1.629 + this.form.appendChild(document.createTextNode(this.options.textAfterControls)); 1.630 + }, 1.631 + hasHTMLLineBreaks: function(string) { 1.632 + if (!this.options.handleLineBreaks) return false; 1.633 + return string.match(/<br/i) || string.match(/<p>/i); 1.634 + }, 1.635 + convertHTMLLineBreaks: function(string) { 1.636 + return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, ""); 1.637 + }, 1.638 + createEditField: function() { 1.639 + var text; 1.640 + if(this.options.loadTextURL) { 1.641 + text = this.options.loadingText; 1.642 + } else { 1.643 + text = this.getText(); 1.644 + } 1.645 + 1.646 + var obj = this; 1.647 + 1.648 + if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) { 1.649 + this.options.textarea = false; 1.650 + var textField = document.createElement("input"); 1.651 + textField.obj = this; 1.652 + textField.type = "text"; 1.653 + textField.name = this.options.paramName; 1.654 + textField.value = text; 1.655 + textField.style.backgroundColor = this.options.highlightcolor; 1.656 + textField.className = 'editor_field'; 1.657 + var size = this.options.size || this.options.cols || 0; 1.658 + if (size != 0) textField.size = size; 1.659 + if (this.options.submitOnBlur) 1.660 + textField.onblur = this.onSubmit.bind(this); 1.661 + this.editField = textField; 1.662 + } else { 1.663 + this.options.textarea = true; 1.664 + var textArea = document.createElement("textarea"); 1.665 + textArea.obj = this; 1.666 + textArea.name = this.options.paramName; 1.667 + textArea.value = this.convertHTMLLineBreaks(text); 1.668 + textArea.rows = this.options.rows; 1.669 + textArea.cols = this.options.cols || 40; 1.670 + textArea.className = 'editor_field'; 1.671 + if (this.options.submitOnBlur) 1.672 + textArea.onblur = this.onSubmit.bind(this); 1.673 + this.editField = textArea; 1.674 + } 1.675 + 1.676 + if(this.options.loadTextURL) { 1.677 + this.loadExternalText(); 1.678 + } 1.679 + this.form.appendChild(this.editField); 1.680 + }, 1.681 + getText: function() { 1.682 + return this.element.innerHTML; 1.683 + }, 1.684 + loadExternalText: function() { 1.685 + Element.addClassName(this.form, this.options.loadingClassName); 1.686 + this.editField.disabled = true; 1.687 + new Ajax.Request( 1.688 + this.options.loadTextURL, 1.689 + Object.extend({ 1.690 + asynchronous: true, 1.691 + onComplete: this.onLoadedExternalText.bind(this) 1.692 + }, this.options.ajaxOptions) 1.693 + ); 1.694 + }, 1.695 + onLoadedExternalText: function(transport) { 1.696 + Element.removeClassName(this.form, this.options.loadingClassName); 1.697 + this.editField.disabled = false; 1.698 + this.editField.value = transport.responseText.stripTags(); 1.699 + Field.scrollFreeActivate(this.editField); 1.700 + }, 1.701 + onclickCancel: function() { 1.702 + this.onComplete(); 1.703 + this.leaveEditMode(); 1.704 + return false; 1.705 + }, 1.706 + onFailure: function(transport) { 1.707 + this.options.onFailure(transport); 1.708 + if (this.oldInnerHTML) { 1.709 + this.element.innerHTML = this.oldInnerHTML; 1.710 + this.oldInnerHTML = null; 1.711 + } 1.712 + return false; 1.713 + }, 1.714 + onSubmit: function() { 1.715 + // onLoading resets these so we need to save them away for the Ajax call 1.716 + var form = this.form; 1.717 + var value = this.editField.value; 1.718 + 1.719 + // do this first, sometimes the ajax call returns before we get a chance to switch on Saving... 1.720 + // which means this will actually switch on Saving... *after* we've left edit mode causing Saving... 1.721 + // to be displayed indefinitely 1.722 + this.onLoading(); 1.723 + 1.724 + if (this.options.evalScripts) { 1.725 + new Ajax.Request( 1.726 + this.url, Object.extend({ 1.727 + parameters: this.options.callback(form, value), 1.728 + onComplete: this.onComplete.bind(this), 1.729 + onFailure: this.onFailure.bind(this), 1.730 + asynchronous:true, 1.731 + evalScripts:true 1.732 + }, this.options.ajaxOptions)); 1.733 + } else { 1.734 + new Ajax.Updater( 1.735 + { success: this.element, 1.736 + // don't update on failure (this could be an option) 1.737 + failure: null }, 1.738 + this.url, Object.extend({ 1.739 + parameters: this.options.callback(form, value), 1.740 + onComplete: this.onComplete.bind(this), 1.741 + onFailure: this.onFailure.bind(this) 1.742 + }, this.options.ajaxOptions)); 1.743 + } 1.744 + // stop the event to avoid a page refresh in Safari 1.745 + if (arguments.length > 1) { 1.746 + Event.stop(arguments[0]); 1.747 + } 1.748 + return false; 1.749 + }, 1.750 + onLoading: function() { 1.751 + this.saving = true; 1.752 + this.removeForm(); 1.753 + this.leaveHover(); 1.754 + this.showSaving(); 1.755 + }, 1.756 + showSaving: function() { 1.757 + this.oldInnerHTML = this.element.innerHTML; 1.758 + this.element.innerHTML = this.options.savingText; 1.759 + Element.addClassName(this.element, this.options.savingClassName); 1.760 + this.element.style.backgroundColor = this.originalBackground; 1.761 + Element.show(this.element); 1.762 + }, 1.763 + removeForm: function() { 1.764 + if(this.form) { 1.765 + if (this.form.parentNode) Element.remove(this.form); 1.766 + this.form = null; 1.767 + } 1.768 + }, 1.769 + enterHover: function() { 1.770 + if (this.saving) return; 1.771 + this.element.style.backgroundColor = this.options.highlightcolor; 1.772 + if (this.effect) { 1.773 + this.effect.cancel(); 1.774 + } 1.775 + Element.addClassName(this.element, this.options.hoverClassName) 1.776 + }, 1.777 + leaveHover: function() { 1.778 + if (this.options.backgroundColor) { 1.779 + this.element.style.backgroundColor = this.oldBackground; 1.780 + } 1.781 + Element.removeClassName(this.element, this.options.hoverClassName) 1.782 + if (this.saving) return; 1.783 + this.effect = new Effect.Highlight(this.element, { 1.784 + startcolor: this.options.highlightcolor, 1.785 + endcolor: this.options.highlightendcolor, 1.786 + restorecolor: this.originalBackground 1.787 + }); 1.788 + }, 1.789 + leaveEditMode: function() { 1.790 + Element.removeClassName(this.element, this.options.savingClassName); 1.791 + this.removeForm(); 1.792 + this.leaveHover(); 1.793 + this.element.style.backgroundColor = this.originalBackground; 1.794 + Element.show(this.element); 1.795 + if (this.options.externalControl) { 1.796 + Element.show(this.options.externalControl); 1.797 + } 1.798 + this.editing = false; 1.799 + this.saving = false; 1.800 + this.oldInnerHTML = null; 1.801 + this.onLeaveEditMode(); 1.802 + }, 1.803 + onComplete: function(transport) { 1.804 + this.leaveEditMode(); 1.805 + this.options.onComplete.bind(this)(transport, this.element); 1.806 + }, 1.807 + onEnterEditMode: function() {}, 1.808 + onLeaveEditMode: function() {}, 1.809 + dispose: function() { 1.810 + if (this.oldInnerHTML) { 1.811 + this.element.innerHTML = this.oldInnerHTML; 1.812 + } 1.813 + this.leaveEditMode(); 1.814 + Event.stopObserving(this.element, 'click', this.onclickListener); 1.815 + Event.stopObserving(this.element, 'mouseover', this.mouseoverListener); 1.816 + Event.stopObserving(this.element, 'mouseout', this.mouseoutListener); 1.817 + if (this.options.externalControl) { 1.818 + Event.stopObserving(this.options.externalControl, 'click', this.onclickListener); 1.819 + Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener); 1.820 + Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener); 1.821 + } 1.822 + } 1.823 +}; 1.824 + 1.825 +Ajax.InPlaceCollectionEditor = Class.create(); 1.826 +Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype); 1.827 +Object.extend(Ajax.InPlaceCollectionEditor.prototype, { 1.828 + createEditField: function() { 1.829 + if (!this.cached_selectTag) { 1.830 + var selectTag = document.createElement("select"); 1.831 + var collection = this.options.collection || []; 1.832 + var optionTag; 1.833 + collection.each(function(e,i) { 1.834 + optionTag = document.createElement("option"); 1.835 + optionTag.value = (e instanceof Array) ? e[0] : e; 1.836 + if((typeof this.options.value == 'undefined') && 1.837 + ((e instanceof Array) ? this.element.innerHTML == e[1] : e == optionTag.value)) optionTag.selected = true; 1.838 + if(this.options.value==optionTag.value) optionTag.selected = true; 1.839 + optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e)); 1.840 + selectTag.appendChild(optionTag); 1.841 + }.bind(this)); 1.842 + this.cached_selectTag = selectTag; 1.843 + } 1.844 + 1.845 + this.editField = this.cached_selectTag; 1.846 + if(this.options.loadTextURL) this.loadExternalText(); 1.847 + this.form.appendChild(this.editField); 1.848 + this.options.callback = function(form, value) { 1.849 + return "value=" + encodeURIComponent(value); 1.850 + } 1.851 + } 1.852 +}); 1.853 + 1.854 +// Delayed observer, like Form.Element.Observer, 1.855 +// but waits for delay after last key input 1.856 +// Ideal for live-search fields 1.857 + 1.858 +Form.Element.DelayedObserver = Class.create(); 1.859 +Form.Element.DelayedObserver.prototype = { 1.860 + initialize: function(element, delay, callback) { 1.861 + this.delay = delay || 0.5; 1.862 + this.element = $(element); 1.863 + this.callback = callback; 1.864 + this.timer = null; 1.865 + this.lastValue = $F(this.element); 1.866 + Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); 1.867 + }, 1.868 + delayedListener: function(event) { 1.869 + if(this.lastValue == $F(this.element)) return; 1.870 + if(this.timer) clearTimeout(this.timer); 1.871 + this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000); 1.872 + this.lastValue = $F(this.element); 1.873 + }, 1.874 + onTimerEvent: function() { 1.875 + this.timer = null; 1.876 + this.callback(this.element, $F(this.element)); 1.877 + } 1.878 +};