controls.js
749 lines
| 24.9 KiB
| application/javascript
|
JavascriptLexer
|
r2 | // Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) | |
// (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan) | |||
// (c) 2005 Jon Tirsen (http://www.tirsen.com) | |||
// Contributors: | |||
// Richard Livsey | |||
// Rahul Bhargava | |||
// Rob Wills | |||
// | |||
// See scriptaculous.js for full license. | |||
// Autocompleter.Base handles all the autocompletion functionality | |||
// that's independent of the data source for autocompletion. This | |||
// includes drawing the autocompletion menu, observing keyboard | |||
// and mouse events, and similar. | |||
// | |||
// Specific autocompleters need to provide, at the very least, | |||
// a getUpdatedChoices function that will be invoked every time | |||
// the text inside the monitored textbox changes. This method | |||
// should get the text for which to provide autocompletion by | |||
// invoking this.getToken(), NOT by directly accessing | |||
// this.element.value. This is to allow incremental tokenized | |||
// autocompletion. Specific auto-completion logic (AJAX, etc) | |||
// belongs in getUpdatedChoices. | |||
// | |||
// Tokenized incremental autocompletion is enabled automatically | |||
// when an autocompleter is instantiated with the 'tokens' option | |||
// in the options parameter, e.g.: | |||
// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); | |||
// will incrementally autocomplete with a comma as the token. | |||
// Additionally, ',' in the above example can be replaced with | |||
// a token array, e.g. { tokens: [',', '\n'] } which | |||
// enables autocompletion on multiple tokens. This is most | |||
// useful when one of the tokens is \n (a newline), as it | |||
// allows smart autocompletion after linebreaks. | |||
var Autocompleter = {} | |||
Autocompleter.Base = function() {}; | |||
Autocompleter.Base.prototype = { | |||
baseInitialize: function(element, update, options) { | |||
this.element = $(element); | |||
this.update = $(update); | |||
this.hasFocus = false; | |||
this.changed = false; | |||
this.active = false; | |||
this.index = 0; | |||
this.entryCount = 0; | |||
if (this.setOptions) | |||
this.setOptions(options); | |||
else | |||
this.options = options || {}; | |||
this.options.paramName = this.options.paramName || this.element.name; | |||
this.options.tokens = this.options.tokens || []; | |||
this.options.frequency = this.options.frequency || 0.4; | |||
this.options.minChars = this.options.minChars || 1; | |||
this.options.onShow = this.options.onShow || | |||
function(element, update){ | |||
if(!update.style.position || update.style.position=='absolute') { | |||
update.style.position = 'absolute'; | |||
Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight}); | |||
} | |||
Effect.Appear(update,{duration:0.15}); | |||
}; | |||
this.options.onHide = this.options.onHide || | |||
function(element, update){ new Effect.Fade(update,{duration:0.15}) }; | |||
if (typeof(this.options.tokens) == 'string') | |||
this.options.tokens = new Array(this.options.tokens); | |||
this.observer = null; | |||
this.element.setAttribute('autocomplete','off'); | |||
Element.hide(this.update); | |||
Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this)); | |||
Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this)); | |||
}, | |||
show: function() { | |||
if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); | |||
if(!this.iefix && | |||
(navigator.appVersion.indexOf('MSIE')>0) && | |||
(navigator.userAgent.indexOf('Opera')<0) && | |||
(Element.getStyle(this.update, 'position')=='absolute')) { | |||
new Insertion.After(this.update, | |||
'<iframe id="' + this.update.id + '_iefix" '+ | |||
'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' + | |||
'src="javascript:false;" frameborder="0" scrolling="no"></iframe>'); | |||
this.iefix = $(this.update.id+'_iefix'); | |||
} | |||
if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50); | |||
}, | |||
fixIEOverlapping: function() { | |||
Position.clone(this.update, this.iefix); | |||
this.iefix.style.zIndex = 1; | |||
this.update.style.zIndex = 2; | |||
Element.show(this.iefix); | |||
}, | |||
hide: function() { | |||
this.stopIndicator(); | |||
if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); | |||
if(this.iefix) Element.hide(this.iefix); | |||
}, | |||
startIndicator: function() { | |||
if(this.options.indicator) Element.show(this.options.indicator); | |||
}, | |||
stopIndicator: function() { | |||
if(this.options.indicator) Element.hide(this.options.indicator); | |||
}, | |||
onKeyPress: function(event) { | |||
if(this.active) | |||
switch(event.keyCode) { | |||
case Event.KEY_TAB: | |||
case Event.KEY_RETURN: | |||
this.selectEntry(); | |||
Event.stop(event); | |||
case Event.KEY_ESC: | |||
this.hide(); | |||
this.active = false; | |||
Event.stop(event); | |||
return; | |||
case Event.KEY_LEFT: | |||
case Event.KEY_RIGHT: | |||
return; | |||
case Event.KEY_UP: | |||
this.markPrevious(); | |||
this.render(); | |||
if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); | |||
return; | |||
case Event.KEY_DOWN: | |||
this.markNext(); | |||
this.render(); | |||
if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); | |||
return; | |||
} | |||
else | |||
if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN) | |||
return; | |||
this.changed = true; | |||
this.hasFocus = true; | |||
if(this.observer) clearTimeout(this.observer); | |||
this.observer = | |||
setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); | |||
}, | |||
onHover: function(event) { | |||
var element = Event.findElement(event, 'LI'); | |||
if(this.index != element.autocompleteIndex) | |||
{ | |||
this.index = element.autocompleteIndex; | |||
this.render(); | |||
} | |||
Event.stop(event); | |||
}, | |||
onClick: function(event) { | |||
var element = Event.findElement(event, 'LI'); | |||
this.index = element.autocompleteIndex; | |||
this.selectEntry(); | |||
this.hide(); | |||
}, | |||
onBlur: function(event) { | |||
// needed to make click events working | |||
setTimeout(this.hide.bind(this), 250); | |||
this.hasFocus = false; | |||
this.active = false; | |||
}, | |||
render: function() { | |||
if(this.entryCount > 0) { | |||
for (var i = 0; i < this.entryCount; i++) | |||
this.index==i ? | |||
Element.addClassName(this.getEntry(i),"selected") : | |||
Element.removeClassName(this.getEntry(i),"selected"); | |||
if(this.hasFocus) { | |||
this.show(); | |||
this.active = true; | |||
} | |||
} else { | |||
this.active = false; | |||
this.hide(); | |||
} | |||
}, | |||
markPrevious: function() { | |||
if(this.index > 0) this.index-- | |||
else this.index = this.entryCount-1; | |||
}, | |||
markNext: function() { | |||
if(this.index < this.entryCount-1) this.index++ | |||
else this.index = 0; | |||
}, | |||
getEntry: function(index) { | |||
return this.update.firstChild.childNodes[index]; | |||
}, | |||
getCurrentEntry: function() { | |||
return this.getEntry(this.index); | |||
}, | |||
selectEntry: function() { | |||
this.active = false; | |||
this.updateElement(this.getCurrentEntry()); | |||
}, | |||
updateElement: function(selectedElement) { | |||
if (this.options.updateElement) { | |||
this.options.updateElement(selectedElement); | |||
return; | |||
} | |||
var value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); | |||
var lastTokenPos = this.findLastToken(); | |||
if (lastTokenPos != -1) { | |||
var newValue = this.element.value.substr(0, lastTokenPos + 1); | |||
var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/); | |||
if (whitespace) | |||
newValue += whitespace[0]; | |||
this.element.value = newValue + value; | |||
} else { | |||
this.element.value = value; | |||
} | |||
this.element.focus(); | |||
if (this.options.afterUpdateElement) | |||
this.options.afterUpdateElement(this.element, selectedElement); | |||
}, | |||
updateChoices: function(choices) { | |||
if(!this.changed && this.hasFocus) { | |||
this.update.innerHTML = choices; | |||
Element.cleanWhitespace(this.update); | |||
Element.cleanWhitespace(this.update.firstChild); | |||
if(this.update.firstChild && this.update.firstChild.childNodes) { | |||
this.entryCount = | |||
this.update.firstChild.childNodes.length; | |||
for (var i = 0; i < this.entryCount; i++) { | |||
var entry = this.getEntry(i); | |||
entry.autocompleteIndex = i; | |||
this.addObservers(entry); | |||
} | |||
} else { | |||
this.entryCount = 0; | |||
} | |||
this.stopIndicator(); | |||
this.index = 0; | |||
this.render(); | |||
} | |||
}, | |||
addObservers: function(element) { | |||
Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); | |||
Event.observe(element, "click", this.onClick.bindAsEventListener(this)); | |||
}, | |||
onObserverEvent: function() { | |||
this.changed = false; | |||
if(this.getToken().length>=this.options.minChars) { | |||
this.startIndicator(); | |||
this.getUpdatedChoices(); | |||
} else { | |||
this.active = false; | |||
this.hide(); | |||
} | |||
}, | |||
getToken: function() { | |||
var tokenPos = this.findLastToken(); | |||
if (tokenPos != -1) | |||
var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,''); | |||
else | |||
var ret = this.element.value; | |||
return /\n/.test(ret) ? '' : ret; | |||
}, | |||
findLastToken: function() { | |||
var lastTokenPos = -1; | |||
for (var i=0; i<this.options.tokens.length; i++) { | |||
var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]); | |||
if (thisTokenPos > lastTokenPos) | |||
lastTokenPos = thisTokenPos; | |||
} | |||
return lastTokenPos; | |||
} | |||
} | |||
Ajax.Autocompleter = Class.create(); | |||
Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), { | |||
initialize: function(element, update, url, options) { | |||
this.baseInitialize(element, update, options); | |||
this.options.asynchronous = true; | |||
this.options.onComplete = this.onComplete.bind(this); | |||
this.options.defaultParams = this.options.parameters || null; | |||
this.url = url; | |||
}, | |||
getUpdatedChoices: function() { | |||
entry = encodeURIComponent(this.options.paramName) + '=' + | |||
encodeURIComponent(this.getToken()); | |||
this.options.parameters = this.options.callback ? | |||
this.options.callback(this.element, entry) : entry; | |||
if(this.options.defaultParams) | |||
this.options.parameters += '&' + this.options.defaultParams; | |||
new Ajax.Request(this.url, this.options); | |||
}, | |||
onComplete: function(request) { | |||
this.updateChoices(request.responseText); | |||
} | |||
}); | |||
// The local array autocompleter. Used when you'd prefer to | |||
// inject an array of autocompletion options into the page, rather | |||
// than sending out Ajax queries, which can be quite slow sometimes. | |||
// | |||
// The constructor takes four parameters. The first two are, as usual, | |||
// the id of the monitored textbox, and id of the autocompletion menu. | |||
// The third is the array you want to autocomplete from, and the fourth | |||
// is the options block. | |||
// | |||
// Extra local autocompletion options: | |||
// - choices - How many autocompletion choices to offer | |||
// | |||
// - partialSearch - If false, the autocompleter will match entered | |||
// text only at the beginning of strings in the | |||
// autocomplete array. Defaults to true, which will | |||
// match text at the beginning of any *word* in the | |||
// strings in the autocomplete array. If you want to | |||
// search anywhere in the string, additionally set | |||
// the option fullSearch to true (default: off). | |||
// | |||
// - fullSsearch - Search anywhere in autocomplete array strings. | |||
// | |||
// - partialChars - How many characters to enter before triggering | |||
// a partial match (unlike minChars, which defines | |||
// how many characters are required to do any match | |||
// at all). Defaults to 2. | |||
// | |||
// - ignoreCase - Whether to ignore case when autocompleting. | |||
// Defaults to true. | |||
// | |||
// It's possible to pass in a custom function as the 'selector' | |||
// option, if you prefer to write your own autocompletion logic. | |||
// In that case, the other options above will not apply unless | |||
// you support them. | |||
Autocompleter.Local = Class.create(); | |||
Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), { | |||
initialize: function(element, update, array, options) { | |||
this.baseInitialize(element, update, options); | |||
this.options.array = array; | |||
}, | |||
getUpdatedChoices: function() { | |||
this.updateChoices(this.options.selector(this)); | |||
}, | |||
setOptions: function(options) { | |||
this.options = Object.extend({ | |||
choices: 10, | |||
partialSearch: true, | |||
partialChars: 2, | |||
ignoreCase: true, | |||
fullSearch: false, | |||
selector: function(instance) { | |||
var ret = []; // Beginning matches | |||
var partial = []; // Inside matches | |||
var entry = instance.getToken(); | |||
var count = 0; | |||
for (var i = 0; i < instance.options.array.length && | |||
ret.length < instance.options.choices ; i++) { | |||
var elem = instance.options.array[i]; | |||
var foundPos = instance.options.ignoreCase ? | |||
elem.toLowerCase().indexOf(entry.toLowerCase()) : | |||
elem.indexOf(entry); | |||
while (foundPos != -1) { | |||
if (foundPos == 0 && elem.length != entry.length) { | |||
ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" + | |||
elem.substr(entry.length) + "</li>"); | |||
break; | |||
} else if (entry.length >= instance.options.partialChars && | |||
instance.options.partialSearch && foundPos != -1) { | |||
if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { | |||
partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" + | |||
elem.substr(foundPos, entry.length) + "</strong>" + elem.substr( | |||
foundPos + entry.length) + "</li>"); | |||
break; | |||
} | |||
} | |||
foundPos = instance.options.ignoreCase ? | |||
elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : | |||
elem.indexOf(entry, foundPos + 1); | |||
} | |||
} | |||
if (partial.length) | |||
ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)) | |||
return "<ul>" + ret.join('') + "</ul>"; | |||
} | |||
}, options || {}); | |||
} | |||
}); | |||
// AJAX in-place editor | |||
// | |||
// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor | |||
// Use this if you notice weird scrolling problems on some browsers, | |||
// the DOM might be a bit confused when this gets called so do this | |||
// waits 1 ms (with setTimeout) until it does the activation | |||
Field.scrollFreeActivate = function(field) { | |||
setTimeout(function() { | |||
Field.activate(field); | |||
}, 1); | |||
} | |||
Ajax.InPlaceEditor = Class.create(); | |||
Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99"; | |||
Ajax.InPlaceEditor.prototype = { | |||
initialize: function(element, url, options) { | |||
this.url = url; | |||
this.element = $(element); | |||
this.options = Object.extend({ | |||
okText: "ok", | |||
cancelText: "cancel", | |||
savingText: "Saving...", | |||
clickToEditText: "Click to edit", | |||
okText: "ok", | |||
rows: 1, | |||
onComplete: function(transport, element) { | |||
new Effect.Highlight(element, {startcolor: this.options.highlightcolor}); | |||
}, | |||
onFailure: function(transport) { | |||
alert("Error communicating with the server: " + transport.responseText.stripTags()); | |||
}, | |||
callback: function(form) { | |||
return Form.serialize(form); | |||
}, | |||
handleLineBreaks: true, | |||
loadingText: 'Loading...', | |||
savingClassName: 'inplaceeditor-saving', | |||
loadingClassName: 'inplaceeditor-loading', | |||
formClassName: 'inplaceeditor-form', | |||
highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor, | |||
highlightendcolor: "#FFFFFF", | |||
externalControl: null, | |||
ajaxOptions: {} | |||
}, options || {}); | |||
if(!this.options.formId && this.element.id) { | |||
this.options.formId = this.element.id + "-inplaceeditor"; | |||
if ($(this.options.formId)) { | |||
// there's already a form with that name, don't specify an id | |||
this.options.formId = null; | |||
} | |||
} | |||
if (this.options.externalControl) { | |||
this.options.externalControl = $(this.options.externalControl); | |||
} | |||
this.originalBackground = Element.getStyle(this.element, 'background-color'); | |||
if (!this.originalBackground) { | |||
this.originalBackground = "transparent"; | |||
} | |||
this.element.title = this.options.clickToEditText; | |||
this.onclickListener = this.enterEditMode.bindAsEventListener(this); | |||
this.mouseoverListener = this.enterHover.bindAsEventListener(this); | |||
this.mouseoutListener = this.leaveHover.bindAsEventListener(this); | |||
Event.observe(this.element, 'click', this.onclickListener); | |||
Event.observe(this.element, 'mouseover', this.mouseoverListener); | |||
Event.observe(this.element, 'mouseout', this.mouseoutListener); | |||
if (this.options.externalControl) { | |||
Event.observe(this.options.externalControl, 'click', this.onclickListener); | |||
Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener); | |||
Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener); | |||
} | |||
}, | |||
enterEditMode: function(evt) { | |||
if (this.saving) return; | |||
if (this.editing) return; | |||
this.editing = true; | |||
this.onEnterEditMode(); | |||
if (this.options.externalControl) { | |||
Element.hide(this.options.externalControl); | |||
} | |||
Element.hide(this.element); | |||
this.createForm(); | |||
this.element.parentNode.insertBefore(this.form, this.element); | |||
Field.scrollFreeActivate(this.editField); | |||
// stop the event to avoid a page refresh in Safari | |||
if (evt) { | |||
Event.stop(evt); | |||
} | |||
return false; | |||
}, | |||
createForm: function() { | |||
this.form = document.createElement("form"); | |||
this.form.id = this.options.formId; | |||
Element.addClassName(this.form, this.options.formClassName) | |||
this.form.onsubmit = this.onSubmit.bind(this); | |||
this.createEditField(); | |||
if (this.options.textarea) { | |||
var br = document.createElement("br"); | |||
this.form.appendChild(br); | |||
} | |||
okButton = document.createElement("input"); | |||
okButton.type = "submit"; | |||
okButton.value = this.options.okText; | |||
this.form.appendChild(okButton); | |||
cancelLink = document.createElement("a"); | |||
cancelLink.href = "#"; | |||
cancelLink.appendChild(document.createTextNode(this.options.cancelText)); | |||
cancelLink.onclick = this.onclickCancel.bind(this); | |||
this.form.appendChild(cancelLink); | |||
}, | |||
hasHTMLLineBreaks: function(string) { | |||
if (!this.options.handleLineBreaks) return false; | |||
return string.match(/<br/i) || string.match(/<p>/i); | |||
}, | |||
convertHTMLLineBreaks: function(string) { | |||
return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, ""); | |||
}, | |||
createEditField: function() { | |||
var text; | |||
if(this.options.loadTextURL) { | |||
text = this.options.loadingText; | |||
} else { | |||
text = this.getText(); | |||
} | |||
if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) { | |||
this.options.textarea = false; | |||
var textField = document.createElement("input"); | |||
textField.type = "text"; | |||
textField.name = "value"; | |||
textField.value = text; | |||
textField.style.backgroundColor = this.options.highlightcolor; | |||
var size = this.options.size || this.options.cols || 0; | |||
if (size != 0) textField.size = size; | |||
this.editField = textField; | |||
} else { | |||
this.options.textarea = true; | |||
var textArea = document.createElement("textarea"); | |||
textArea.name = "value"; | |||
textArea.value = this.convertHTMLLineBreaks(text); | |||
textArea.rows = this.options.rows; | |||
textArea.cols = this.options.cols || 40; | |||
this.editField = textArea; | |||
} | |||
if(this.options.loadTextURL) { | |||
this.loadExternalText(); | |||
} | |||
this.form.appendChild(this.editField); | |||
}, | |||
getText: function() { | |||
return this.element.innerHTML; | |||
}, | |||
loadExternalText: function() { | |||
Element.addClassName(this.form, this.options.loadingClassName); | |||
this.editField.disabled = true; | |||
new Ajax.Request( | |||
this.options.loadTextURL, | |||
Object.extend({ | |||
asynchronous: true, | |||
onComplete: this.onLoadedExternalText.bind(this) | |||
}, this.options.ajaxOptions) | |||
); | |||
}, | |||
onLoadedExternalText: function(transport) { | |||
Element.removeClassName(this.form, this.options.loadingClassName); | |||
this.editField.disabled = false; | |||
this.editField.value = transport.responseText.stripTags(); | |||
}, | |||
onclickCancel: function() { | |||
this.onComplete(); | |||
this.leaveEditMode(); | |||
return false; | |||
}, | |||
onFailure: function(transport) { | |||
this.options.onFailure(transport); | |||
if (this.oldInnerHTML) { | |||
this.element.innerHTML = this.oldInnerHTML; | |||
this.oldInnerHTML = null; | |||
} | |||
return false; | |||
}, | |||
onSubmit: function() { | |||
// onLoading resets these so we need to save them away for the Ajax call | |||
var form = this.form; | |||
var value = this.editField.value; | |||
// do this first, sometimes the ajax call returns before we get a chance to switch on Saving... | |||
// which means this will actually switch on Saving... *after* we've left edit mode causing Saving... | |||
// to be displayed indefinitely | |||
this.onLoading(); | |||
new Ajax.Updater( | |||
{ | |||
success: this.element, | |||
// don't update on failure (this could be an option) | |||
failure: null | |||
}, | |||
this.url, | |||
Object.extend({ | |||
parameters: this.options.callback(form, value), | |||
onComplete: this.onComplete.bind(this), | |||
onFailure: this.onFailure.bind(this) | |||
}, this.options.ajaxOptions) | |||
); | |||
// stop the event to avoid a page refresh in Safari | |||
if (arguments.length > 1) { | |||
Event.stop(arguments[0]); | |||
} | |||
return false; | |||
}, | |||
onLoading: function() { | |||
this.saving = true; | |||
this.removeForm(); | |||
this.leaveHover(); | |||
this.showSaving(); | |||
}, | |||
showSaving: function() { | |||
this.oldInnerHTML = this.element.innerHTML; | |||
this.element.innerHTML = this.options.savingText; | |||
Element.addClassName(this.element, this.options.savingClassName); | |||
this.element.style.backgroundColor = this.originalBackground; | |||
Element.show(this.element); | |||
}, | |||
removeForm: function() { | |||
if(this.form) { | |||
if (this.form.parentNode) Element.remove(this.form); | |||
this.form = null; | |||
} | |||
}, | |||
enterHover: function() { | |||
if (this.saving) return; | |||
this.element.style.backgroundColor = this.options.highlightcolor; | |||
if (this.effect) { | |||
this.effect.cancel(); | |||
} | |||
Element.addClassName(this.element, this.options.hoverClassName) | |||
}, | |||
leaveHover: function() { | |||
if (this.options.backgroundColor) { | |||
this.element.style.backgroundColor = this.oldBackground; | |||
} | |||
Element.removeClassName(this.element, this.options.hoverClassName) | |||
if (this.saving) return; | |||
this.effect = new Effect.Highlight(this.element, { | |||
startcolor: this.options.highlightcolor, | |||
endcolor: this.options.highlightendcolor, | |||
restorecolor: this.originalBackground | |||
}); | |||
}, | |||
leaveEditMode: function() { | |||
Element.removeClassName(this.element, this.options.savingClassName); | |||
this.removeForm(); | |||
this.leaveHover(); | |||
this.element.style.backgroundColor = this.originalBackground; | |||
Element.show(this.element); | |||
if (this.options.externalControl) { | |||
Element.show(this.options.externalControl); | |||
} | |||
this.editing = false; | |||
this.saving = false; | |||
this.oldInnerHTML = null; | |||
this.onLeaveEditMode(); | |||
}, | |||
onComplete: function(transport) { | |||
this.leaveEditMode(); | |||
this.options.onComplete.bind(this)(transport, this.element); | |||
}, | |||
onEnterEditMode: function() {}, | |||
onLeaveEditMode: function() {}, | |||
dispose: function() { | |||
if (this.oldInnerHTML) { | |||
this.element.innerHTML = this.oldInnerHTML; | |||
} | |||
this.leaveEditMode(); | |||
Event.stopObserving(this.element, 'click', this.onclickListener); | |||
Event.stopObserving(this.element, 'mouseover', this.mouseoverListener); | |||
Event.stopObserving(this.element, 'mouseout', this.mouseoutListener); | |||
if (this.options.externalControl) { | |||
Event.stopObserving(this.options.externalControl, 'click', this.onclickListener); | |||
Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener); | |||
Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener); | |||
} | |||
} | |||
}; | |||
// Delayed observer, like Form.Element.Observer, | |||
// but waits for delay after last key input | |||
// Ideal for live-search fields | |||
Form.Element.DelayedObserver = Class.create(); | |||
Form.Element.DelayedObserver.prototype = { | |||
initialize: function(element, delay, callback) { | |||
this.delay = delay || 0.5; | |||
this.element = $(element); | |||
this.callback = callback; | |||
this.timer = null; | |||
this.lastValue = $F(this.element); | |||
Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); | |||
}, | |||
delayedListener: function(event) { | |||
if(this.lastValue == $F(this.element)) return; | |||
if(this.timer) clearTimeout(this.timer); | |||
this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000); | |||
this.lastValue = $F(this.element); | |||
}, | |||
onTimerEvent: function() { | |||
this.timer = null; | |||
this.callback(this.element, $F(this.element)); | |||
} | |||
}; |