michael@0: /*** michael@0: Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) michael@0: (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan) michael@0: (c) 2005 Jon Tirsen (http://www.tirsen.com) michael@0: Contributors: michael@0: Richard Livsey michael@0: Rahul Bhargava michael@0: Rob Wills michael@0: Mochi-ized By Thomas Herve (_firstname_@nimail.org) michael@0: michael@0: See scriptaculous.js for full license. michael@0: michael@0: Autocompleter.Base handles all the autocompletion functionality michael@0: that's independent of the data source for autocompletion. This michael@0: includes drawing the autocompletion menu, observing keyboard michael@0: and mouse events, and similar. michael@0: michael@0: Specific autocompleters need to provide, at the very least, michael@0: a getUpdatedChoices function that will be invoked every time michael@0: the text inside the monitored textbox changes. This method michael@0: should get the text for which to provide autocompletion by michael@0: invoking this.getToken(), NOT by directly accessing michael@0: this.element.value. This is to allow incremental tokenized michael@0: autocompletion. Specific auto-completion logic (AJAX, etc) michael@0: belongs in getUpdatedChoices. michael@0: michael@0: Tokenized incremental autocompletion is enabled automatically michael@0: when an autocompleter is instantiated with the 'tokens' option michael@0: in the options parameter, e.g.: michael@0: new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); michael@0: will incrementally autocomplete with a comma as the token. michael@0: Additionally, ',' in the above example can be replaced with michael@0: a token array, e.g. { tokens: [',', '\n'] } which michael@0: enables autocompletion on multiple tokens. This is most michael@0: useful when one of the tokens is \n (a newline), as it michael@0: allows smart autocompletion after linebreaks. michael@0: michael@0: ***/ michael@0: michael@0: MochiKit.Base.update(MochiKit.Base, { michael@0: ScriptFragment: '(?:)((\n|\r|.)*?)(?:<\/script>)', michael@0: michael@0: /** @id MochiKit.Base.stripScripts */ michael@0: stripScripts: function (str) { michael@0: return str.replace(new RegExp(MochiKit.Base.ScriptFragment, 'img'), ''); michael@0: }, michael@0: michael@0: /** @id MochiKit.Base.stripTags */ michael@0: stripTags: function(str) { michael@0: return str.replace(/<\/?[^>]+>/gi, ''); michael@0: }, michael@0: michael@0: /** @id MochiKit.Base.extractScripts */ michael@0: extractScripts: function (str) { michael@0: var matchAll = new RegExp(MochiKit.Base.ScriptFragment, 'img'); michael@0: var matchOne = new RegExp(MochiKit.Base.ScriptFragment, 'im'); michael@0: return MochiKit.Base.map(function (scriptTag) { michael@0: return (scriptTag.match(matchOne) || ['', ''])[1]; michael@0: }, str.match(matchAll) || []); michael@0: }, michael@0: michael@0: /** @id MochiKit.Base.evalScripts */ michael@0: evalScripts: function (str) { michael@0: return MochiKit.Base.map(function (scr) { michael@0: eval(scr); michael@0: }, MochiKit.Base.extractScripts(str)); michael@0: } michael@0: }); michael@0: michael@0: MochiKit.Form = { michael@0: michael@0: /** @id MochiKit.Form.serialize */ michael@0: serialize: function (form) { michael@0: var elements = MochiKit.Form.getElements(form); michael@0: var queryComponents = []; michael@0: michael@0: for (var i = 0; i < elements.length; i++) { michael@0: var queryComponent = MochiKit.Form.serializeElement(elements[i]); michael@0: if (queryComponent) { michael@0: queryComponents.push(queryComponent); michael@0: } michael@0: } michael@0: michael@0: return queryComponents.join('&'); michael@0: }, michael@0: michael@0: /** @id MochiKit.Form.getElements */ michael@0: getElements: function (form) { michael@0: form = MochiKit.DOM.getElement(form); michael@0: var elements = []; michael@0: michael@0: for (tagName in MochiKit.Form.Serializers) { michael@0: var tagElements = form.getElementsByTagName(tagName); michael@0: for (var j = 0; j < tagElements.length; j++) { michael@0: elements.push(tagElements[j]); michael@0: } michael@0: } michael@0: return elements; michael@0: }, michael@0: michael@0: /** @id MochiKit.Form.serializeElement */ michael@0: serializeElement: function (element) { michael@0: element = MochiKit.DOM.getElement(element); michael@0: var method = element.tagName.toLowerCase(); michael@0: var parameter = MochiKit.Form.Serializers[method](element); michael@0: michael@0: if (parameter) { michael@0: var key = encodeURIComponent(parameter[0]); michael@0: if (key.length === 0) { michael@0: return; michael@0: } michael@0: michael@0: if (!(parameter[1] instanceof Array)) { michael@0: parameter[1] = [parameter[1]]; michael@0: } michael@0: michael@0: return parameter[1].map(function (value) { michael@0: return key + '=' + encodeURIComponent(value); michael@0: }).join('&'); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: MochiKit.Form.Serializers = { michael@0: michael@0: /** @id MochiKit.Form.Serializers.input */ michael@0: input: function (element) { michael@0: switch (element.type.toLowerCase()) { michael@0: case 'submit': michael@0: case 'hidden': michael@0: case 'password': michael@0: case 'text': michael@0: return MochiKit.Form.Serializers.textarea(element); michael@0: case 'checkbox': michael@0: case 'radio': michael@0: return MochiKit.Form.Serializers.inputSelector(element); michael@0: } michael@0: return false; michael@0: }, michael@0: michael@0: /** @id MochiKit.Form.Serializers.inputSelector */ michael@0: inputSelector: function (element) { michael@0: if (element.checked) { michael@0: return [element.name, element.value]; michael@0: } michael@0: }, michael@0: michael@0: /** @id MochiKit.Form.Serializers.textarea */ michael@0: textarea: function (element) { michael@0: return [element.name, element.value]; michael@0: }, michael@0: michael@0: /** @id MochiKit.Form.Serializers.select */ michael@0: select: function (element) { michael@0: return MochiKit.Form.Serializers[element.type == 'select-one' ? michael@0: 'selectOne' : 'selectMany'](element); michael@0: }, michael@0: michael@0: /** @id MochiKit.Form.Serializers.selectOne */ michael@0: selectOne: function (element) { michael@0: var value = '', opt, index = element.selectedIndex; michael@0: if (index >= 0) { michael@0: opt = element.options[index]; michael@0: value = opt.value; michael@0: if (!value && !('value' in opt)) { michael@0: value = opt.text; michael@0: } michael@0: } michael@0: return [element.name, value]; michael@0: }, michael@0: michael@0: /** @id MochiKit.Form.Serializers.selectMany */ michael@0: selectMany: function (element) { michael@0: var value = []; michael@0: for (var i = 0; i < element.length; i++) { michael@0: var opt = element.options[i]; michael@0: if (opt.selected) { michael@0: var optValue = opt.value; michael@0: if (!optValue && !('value' in opt)) { michael@0: optValue = opt.text; michael@0: } michael@0: value.push(optValue); michael@0: } michael@0: } michael@0: return [element.name, value]; michael@0: } michael@0: }; michael@0: michael@0: /** @id Ajax */ michael@0: var Ajax = { michael@0: activeRequestCount: 0 michael@0: }; michael@0: michael@0: Ajax.Responders = { michael@0: responders: [], michael@0: michael@0: /** @id Ajax.Responders.register */ michael@0: register: function (responderToAdd) { michael@0: if (MochiKit.Base.find(this.responders, responderToAdd) == -1) { michael@0: this.responders.push(responderToAdd); michael@0: } michael@0: }, michael@0: michael@0: /** @id Ajax.Responders.unregister */ michael@0: unregister: function (responderToRemove) { michael@0: this.responders = this.responders.without(responderToRemove); michael@0: }, michael@0: michael@0: /** @id Ajax.Responders.dispatch */ michael@0: dispatch: function (callback, request, transport, json) { michael@0: MochiKit.Iter.forEach(this.responders, function (responder) { michael@0: if (responder[callback] && michael@0: typeof(responder[callback]) == 'function') { michael@0: try { michael@0: responder[callback].apply(responder, [request, transport, json]); michael@0: } catch (e) {} michael@0: } michael@0: }); michael@0: } michael@0: }; michael@0: michael@0: Ajax.Responders.register({ michael@0: michael@0: /** @id Ajax.Responders.onCreate */ michael@0: onCreate: function () { michael@0: Ajax.activeRequestCount++; michael@0: }, michael@0: michael@0: /** @id Ajax.Responders.onComplete */ michael@0: onComplete: function () { michael@0: Ajax.activeRequestCount--; michael@0: } michael@0: }); michael@0: michael@0: /** @id Ajax.Base */ michael@0: Ajax.Base = function () {}; michael@0: michael@0: Ajax.Base.prototype = { michael@0: michael@0: /** @id Ajax.Base.prototype.setOptions */ michael@0: setOptions: function (options) { michael@0: this.options = { michael@0: method: 'post', michael@0: asynchronous: true, michael@0: parameters: '' michael@0: } michael@0: MochiKit.Base.update(this.options, options || {}); michael@0: }, michael@0: michael@0: /** @id Ajax.Base.prototype.responseIsSuccess */ michael@0: responseIsSuccess: function () { michael@0: return this.transport.status == undefined michael@0: || this.transport.status === 0 michael@0: || (this.transport.status >= 200 && this.transport.status < 300); michael@0: }, michael@0: michael@0: /** @id Ajax.Base.prototype.responseIsFailure */ michael@0: responseIsFailure: function () { michael@0: return !this.responseIsSuccess(); michael@0: } michael@0: }; michael@0: michael@0: /** @id Ajax.Request */ michael@0: Ajax.Request = function (url, options) { michael@0: this.__init__(url, options); michael@0: }; michael@0: michael@0: /** @id Ajax.Events */ michael@0: Ajax.Request.Events = ['Uninitialized', 'Loading', 'Loaded', michael@0: 'Interactive', 'Complete']; michael@0: michael@0: MochiKit.Base.update(Ajax.Request.prototype, Ajax.Base.prototype); michael@0: michael@0: MochiKit.Base.update(Ajax.Request.prototype, { michael@0: __init__: function (url, options) { michael@0: this.transport = MochiKit.Async.getXMLHttpRequest(); michael@0: this.setOptions(options); michael@0: this.request(url); michael@0: }, michael@0: michael@0: /** @id Ajax.Request.prototype.request */ michael@0: request: function (url) { michael@0: var parameters = this.options.parameters || ''; michael@0: if (parameters.length > 0){ michael@0: parameters += '&_='; michael@0: } michael@0: michael@0: try { michael@0: this.url = url; michael@0: if (this.options.method == 'get' && parameters.length > 0) { michael@0: this.url += (this.url.match(/\?/) ? '&' : '?') + parameters; michael@0: } michael@0: Ajax.Responders.dispatch('onCreate', this, this.transport); michael@0: michael@0: this.transport.open(this.options.method, this.url, michael@0: this.options.asynchronous); michael@0: michael@0: if (this.options.asynchronous) { michael@0: this.transport.onreadystatechange = MochiKit.Base.bind(this.onStateChange, this); michael@0: setTimeout(MochiKit.Base.bind(function () { michael@0: this.respondToReadyState(1); michael@0: }, this), 10); michael@0: } michael@0: michael@0: this.setRequestHeaders(); michael@0: michael@0: var body = this.options.postBody ? this.options.postBody : parameters; michael@0: this.transport.send(this.options.method == 'post' ? body : null); michael@0: michael@0: } catch (e) { michael@0: this.dispatchException(e); michael@0: } michael@0: }, michael@0: michael@0: /** @id Ajax.Request.prototype.setRequestHeaders */ michael@0: setRequestHeaders: function () { michael@0: var requestHeaders = ['X-Requested-With', 'XMLHttpRequest']; michael@0: michael@0: if (this.options.method == 'post') { michael@0: requestHeaders.push('Content-type', michael@0: 'application/x-www-form-urlencoded'); michael@0: michael@0: /* Force 'Connection: close' for Mozilla browsers to work around michael@0: * a bug where XMLHttpRequest sends an incorrect Content-length michael@0: * header. See Mozilla Bugzilla #246651. michael@0: */ michael@0: if (this.transport.overrideMimeType) { michael@0: requestHeaders.push('Connection', 'close'); michael@0: } michael@0: } michael@0: michael@0: if (this.options.requestHeaders) { michael@0: requestHeaders.push.apply(requestHeaders, this.options.requestHeaders); michael@0: } michael@0: michael@0: for (var i = 0; i < requestHeaders.length; i += 2) { michael@0: this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]); michael@0: } michael@0: }, michael@0: michael@0: /** @id Ajax.Request.prototype.onStateChange */ michael@0: onStateChange: function () { michael@0: var readyState = this.transport.readyState; michael@0: if (readyState != 1) { michael@0: this.respondToReadyState(this.transport.readyState); michael@0: } michael@0: }, michael@0: michael@0: /** @id Ajax.Request.prototype.header */ michael@0: header: function (name) { michael@0: try { michael@0: return this.transport.getResponseHeader(name); michael@0: } catch (e) {} michael@0: }, michael@0: michael@0: /** @id Ajax.Request.prototype.evalJSON */ michael@0: evalJSON: function () { michael@0: try { michael@0: return eval(this.header('X-JSON')); michael@0: } catch (e) {} michael@0: }, michael@0: michael@0: /** @id Ajax.Request.prototype.evalResponse */ michael@0: evalResponse: function () { michael@0: try { michael@0: return eval(this.transport.responseText); michael@0: } catch (e) { michael@0: this.dispatchException(e); michael@0: } michael@0: }, michael@0: michael@0: /** @id Ajax.Request.prototype.respondToReadyState */ michael@0: respondToReadyState: function (readyState) { michael@0: var event = Ajax.Request.Events[readyState]; michael@0: var transport = this.transport, json = this.evalJSON(); michael@0: michael@0: if (event == 'Complete') { michael@0: try { michael@0: (this.options['on' + this.transport.status] michael@0: || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')] michael@0: || MochiKit.Base.noop)(transport, json); michael@0: } catch (e) { michael@0: this.dispatchException(e); michael@0: } michael@0: michael@0: if ((this.header('Content-type') || '').match(/^text\/javascript/i)) { michael@0: this.evalResponse(); michael@0: } michael@0: } michael@0: michael@0: try { michael@0: (this.options['on' + event] || MochiKit.Base.noop)(transport, json); michael@0: Ajax.Responders.dispatch('on' + event, this, transport, json); michael@0: } catch (e) { michael@0: this.dispatchException(e); michael@0: } michael@0: michael@0: /* Avoid memory leak in MSIE: clean up the oncomplete event handler */ michael@0: if (event == 'Complete') { michael@0: this.transport.onreadystatechange = MochiKit.Base.noop; michael@0: } michael@0: }, michael@0: michael@0: /** @id Ajax.Request.prototype.dispatchException */ michael@0: dispatchException: function (exception) { michael@0: (this.options.onException || MochiKit.Base.noop)(this, exception); michael@0: Ajax.Responders.dispatch('onException', this, exception); michael@0: } michael@0: }); michael@0: michael@0: /** @id Ajax.Updater */ michael@0: Ajax.Updater = function (container, url, options) { michael@0: this.__init__(container, url, options); michael@0: }; michael@0: michael@0: MochiKit.Base.update(Ajax.Updater.prototype, Ajax.Request.prototype); michael@0: michael@0: MochiKit.Base.update(Ajax.Updater.prototype, { michael@0: __init__: function (container, url, options) { michael@0: this.containers = { michael@0: success: container.success ? MochiKit.DOM.getElement(container.success) : MochiKit.DOM.getElement(container), michael@0: failure: container.failure ? MochiKit.DOM.getElement(container.failure) : michael@0: (container.success ? null : MochiKit.DOM.getElement(container)) michael@0: } michael@0: this.transport = MochiKit.Async.getXMLHttpRequest(); michael@0: this.setOptions(options); michael@0: michael@0: var onComplete = this.options.onComplete || MochiKit.Base.noop; michael@0: this.options.onComplete = MochiKit.Base.bind(function (transport, object) { michael@0: this.updateContent(); michael@0: onComplete(transport, object); michael@0: }, this); michael@0: michael@0: this.request(url); michael@0: }, michael@0: michael@0: /** @id Ajax.Updater.prototype.updateContent */ michael@0: updateContent: function () { michael@0: var receiver = this.responseIsSuccess() ? michael@0: this.containers.success : this.containers.failure; michael@0: var response = this.transport.responseText; michael@0: michael@0: if (!this.options.evalScripts) { michael@0: response = MochiKit.Base.stripScripts(response); michael@0: } michael@0: michael@0: if (receiver) { michael@0: if (this.options.insertion) { michael@0: new this.options.insertion(receiver, response); michael@0: } else { michael@0: MochiKit.DOM.getElement(receiver).innerHTML = michael@0: MochiKit.Base.stripScripts(response); michael@0: setTimeout(function () { michael@0: MochiKit.Base.evalScripts(response); michael@0: }, 10); michael@0: } michael@0: } michael@0: michael@0: if (this.responseIsSuccess()) { michael@0: if (this.onComplete) { michael@0: setTimeout(MochiKit.Base.bind(this.onComplete, this), 10); michael@0: } michael@0: } michael@0: } michael@0: }); michael@0: michael@0: /** @id Field */ michael@0: var Field = { michael@0: michael@0: /** @id clear */ michael@0: clear: function () { michael@0: for (var i = 0; i < arguments.length; i++) { michael@0: MochiKit.DOM.getElement(arguments[i]).value = ''; michael@0: } michael@0: }, michael@0: michael@0: /** @id focus */ michael@0: focus: function (element) { michael@0: MochiKit.DOM.getElement(element).focus(); michael@0: }, michael@0: michael@0: /** @id present */ michael@0: present: function () { michael@0: for (var i = 0; i < arguments.length; i++) { michael@0: if (MochiKit.DOM.getElement(arguments[i]).value == '') { michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: }, michael@0: michael@0: /** @id select */ michael@0: select: function (element) { michael@0: MochiKit.DOM.getElement(element).select(); michael@0: }, michael@0: michael@0: /** @id activate */ michael@0: activate: function (element) { michael@0: element = MochiKit.DOM.getElement(element); michael@0: element.focus(); michael@0: if (element.select) { michael@0: element.select(); michael@0: } michael@0: }, michael@0: michael@0: /** @id scrollFreeActivate */ michael@0: scrollFreeActivate: function (field) { michael@0: setTimeout(function () { michael@0: Field.activate(field); michael@0: }, 1); michael@0: } michael@0: }; michael@0: michael@0: michael@0: /** @id Autocompleter */ michael@0: var Autocompleter = {}; michael@0: michael@0: /** @id Autocompleter.Base */ michael@0: Autocompleter.Base = function () {}; michael@0: michael@0: Autocompleter.Base.prototype = { michael@0: michael@0: /** @id Autocompleter.Base.prototype.baseInitialize */ michael@0: baseInitialize: function (element, update, options) { michael@0: this.element = MochiKit.DOM.getElement(element); michael@0: this.update = MochiKit.DOM.getElement(update); michael@0: this.hasFocus = false; michael@0: this.changed = false; michael@0: this.active = false; michael@0: this.index = 0; michael@0: this.entryCount = 0; michael@0: michael@0: if (this.setOptions) { michael@0: this.setOptions(options); michael@0: } michael@0: else { michael@0: this.options = options || {}; michael@0: } michael@0: michael@0: this.options.paramName = this.options.paramName || this.element.name; michael@0: this.options.tokens = this.options.tokens || []; michael@0: this.options.frequency = this.options.frequency || 0.4; michael@0: this.options.minChars = this.options.minChars || 1; michael@0: this.options.onShow = this.options.onShow || function (element, update) { michael@0: if (!update.style.position || update.style.position == 'absolute') { michael@0: update.style.position = 'absolute'; michael@0: MochiKit.Position.clone(element, update, { michael@0: setHeight: false, michael@0: offsetTop: element.offsetHeight michael@0: }); michael@0: } michael@0: MochiKit.Visual.appear(update, {duration:0.15}); michael@0: }; michael@0: this.options.onHide = this.options.onHide || function (element, update) { michael@0: MochiKit.Visual.fade(update, {duration: 0.15}); michael@0: }; michael@0: michael@0: if (typeof(this.options.tokens) == 'string') { michael@0: this.options.tokens = new Array(this.options.tokens); michael@0: } michael@0: michael@0: this.observer = null; michael@0: michael@0: this.element.setAttribute('autocomplete', 'off'); michael@0: michael@0: MochiKit.Style.hideElement(this.update); michael@0: michael@0: MochiKit.Signal.connect(this.element, 'onblur', this, this.onBlur); michael@0: MochiKit.Signal.connect(this.element, 'onkeypress', this, this.onKeyPress, this); michael@0: }, michael@0: michael@0: /** @id Autocompleter.Base.prototype.show */ michael@0: show: function () { michael@0: if (MochiKit.Style.getStyle(this.update, 'display') == 'none') { michael@0: this.options.onShow(this.element, this.update); michael@0: } michael@0: if (!this.iefix && /MSIE/.test(navigator.userAgent && michael@0: (MochiKit.Style.getStyle(this.update, 'position') == 'absolute')) { michael@0: new Insertion.After(this.update, michael@0: ''); michael@0: this.iefix = MochiKit.DOM.getElement(this.update.id + '_iefix'); michael@0: } michael@0: if (this.iefix) { michael@0: setTimeout(MochiKit.Base.bind(this.fixIEOverlapping, this), 50); michael@0: } michael@0: }, michael@0: michael@0: /** @id Autocompleter.Base.prototype.fixIEOverlapping */ michael@0: fixIEOverlapping: function () { michael@0: MochiKit.Position.clone(this.update, this.iefix); michael@0: this.iefix.style.zIndex = 1; michael@0: this.update.style.zIndex = 2; michael@0: MochiKit.Style.showElement(this.iefix); michael@0: }, michael@0: michael@0: /** @id Autocompleter.Base.prototype.hide */ michael@0: hide: function () { michael@0: this.stopIndicator(); michael@0: if (MochiKit.Style.getStyle(this.update, 'display') != 'none') { michael@0: this.options.onHide(this.element, this.update); michael@0: } michael@0: if (this.iefix) { michael@0: MochiKit.Style.hideElement(this.iefix); michael@0: } michael@0: }, michael@0: michael@0: /** @id Autocompleter.Base.prototype.startIndicator */ michael@0: startIndicator: function () { michael@0: if (this.options.indicator) { michael@0: MochiKit.Style.showElement(this.options.indicator); michael@0: } michael@0: }, michael@0: michael@0: /** @id Autocompleter.Base.prototype.stopIndicator */ michael@0: stopIndicator: function () { michael@0: if (this.options.indicator) { michael@0: MochiKit.Style.hideElement(this.options.indicator); michael@0: } michael@0: }, michael@0: michael@0: /** @id Autocompleter.Base.prototype.onKeyPress */ michael@0: onKeyPress: function (event) { michael@0: if (this.active) { michael@0: if (event.key().string == "KEY_TAB" || event.key().string == "KEY_RETURN") { michael@0: this.selectEntry(); michael@0: MochiKit.Event.stop(event); michael@0: } else if (event.key().string == "KEY_ESCAPE") { michael@0: this.hide(); michael@0: this.active = false; michael@0: MochiKit.Event.stop(event); michael@0: return; michael@0: } else if (event.key().string == "KEY_LEFT" || event.key().string == "KEY_RIGHT") { michael@0: return; michael@0: } else if (event.key().string == "KEY_UP") { michael@0: this.markPrevious(); michael@0: this.render(); michael@0: if (/AppleWebKit'/.test(navigator.appVersion)) { michael@0: event.stop(); michael@0: } michael@0: return; michael@0: } else if (event.key().string == "KEY_DOWN") { michael@0: this.markNext(); michael@0: this.render(); michael@0: if (/AppleWebKit'/.test(navigator.appVersion)) { michael@0: event.stop(); michael@0: } michael@0: return; michael@0: } michael@0: } else { michael@0: if (event.key().string == "KEY_TAB" || event.key().string == "KEY_RETURN") { michael@0: return; michael@0: } michael@0: } michael@0: michael@0: this.changed = true; michael@0: this.hasFocus = true; michael@0: michael@0: if (this.observer) { michael@0: clearTimeout(this.observer); michael@0: } michael@0: this.observer = setTimeout(MochiKit.Base.bind(this.onObserverEvent, this), michael@0: this.options.frequency*1000); michael@0: }, michael@0: michael@0: /** @id Autocompleter.Base.prototype.findElement */ michael@0: findElement: function (event, tagName) { michael@0: var element = event.target; michael@0: while (element.parentNode && (!element.tagName || michael@0: (element.tagName.toUpperCase() != tagName.toUpperCase()))) { michael@0: element = element.parentNode; michael@0: } michael@0: return element; michael@0: }, michael@0: michael@0: /** @id Autocompleter.Base.prototype.hover */ michael@0: onHover: function (event) { michael@0: var element = this.findElement(event, 'LI'); michael@0: if (this.index != element.autocompleteIndex) { michael@0: this.index = element.autocompleteIndex; michael@0: this.render(); michael@0: } michael@0: event.stop(); michael@0: }, michael@0: michael@0: /** @id Autocompleter.Base.prototype.onClick */ michael@0: onClick: function (event) { michael@0: var element = this.findElement(event, 'LI'); michael@0: this.index = element.autocompleteIndex; michael@0: this.selectEntry(); michael@0: this.hide(); michael@0: }, michael@0: michael@0: /** @id Autocompleter.Base.prototype.onBlur */ michael@0: onBlur: function (event) { michael@0: // needed to make click events working michael@0: setTimeout(MochiKit.Base.bind(this.hide, this), 250); michael@0: this.hasFocus = false; michael@0: this.active = false; michael@0: }, michael@0: michael@0: /** @id Autocompleter.Base.prototype.render */ michael@0: render: function () { michael@0: if (this.entryCount > 0) { michael@0: for (var i = 0; i < this.entryCount; i++) { michael@0: this.index == i ? michael@0: MochiKit.DOM.addElementClass(this.getEntry(i), 'selected') : michael@0: MochiKit.DOM.removeElementClass(this.getEntry(i), 'selected'); michael@0: } michael@0: if (this.hasFocus) { michael@0: this.show(); michael@0: this.active = true; michael@0: } michael@0: } else { michael@0: this.active = false; michael@0: this.hide(); michael@0: } michael@0: }, michael@0: michael@0: /** @id Autocompleter.Base.prototype.markPrevious */ michael@0: markPrevious: function () { michael@0: if (this.index > 0) { michael@0: this.index-- michael@0: } else { michael@0: this.index = this.entryCount-1; michael@0: } michael@0: }, michael@0: michael@0: /** @id Autocompleter.Base.prototype.markNext */ michael@0: markNext: function () { michael@0: if (this.index < this.entryCount-1) { michael@0: this.index++ michael@0: } else { michael@0: this.index = 0; michael@0: } michael@0: }, michael@0: michael@0: /** @id Autocompleter.Base.prototype.getEntry */ michael@0: getEntry: function (index) { michael@0: return this.update.firstChild.childNodes[index]; michael@0: }, michael@0: michael@0: /** @id Autocompleter.Base.prototype.getCurrentEntry */ michael@0: getCurrentEntry: function () { michael@0: return this.getEntry(this.index); michael@0: }, michael@0: michael@0: /** @id Autocompleter.Base.prototype.selectEntry */ michael@0: selectEntry: function () { michael@0: this.active = false; michael@0: this.updateElement(this.getCurrentEntry()); michael@0: }, michael@0: michael@0: /** @id Autocompleter.Base.prototype.collectTextNodesIgnoreClass */ michael@0: collectTextNodesIgnoreClass: function (element, className) { michael@0: return MochiKit.Base.flattenArray(MochiKit.Base.map(function (node) { michael@0: if (node.nodeType == 3) { michael@0: return node.nodeValue; michael@0: } else if (node.hasChildNodes() && !MochiKit.DOM.hasElementClass(node, className)) { michael@0: return this.collectTextNodesIgnoreClass(node, className); michael@0: } michael@0: return ''; michael@0: }, MochiKit.DOM.getElement(element).childNodes)).join(''); michael@0: }, michael@0: michael@0: /** @id Autocompleter.Base.prototype.updateElement */ michael@0: updateElement: function (selectedElement) { michael@0: if (this.options.updateElement) { michael@0: this.options.updateElement(selectedElement); michael@0: return; michael@0: } michael@0: var value = ''; michael@0: if (this.options.select) { michael@0: var nodes = document.getElementsByClassName(this.options.select, selectedElement) || []; michael@0: if (nodes.length > 0) { michael@0: value = MochiKit.DOM.scrapeText(nodes[0]); michael@0: } michael@0: } else { michael@0: value = this.collectTextNodesIgnoreClass(selectedElement, 'informal'); michael@0: } michael@0: var lastTokenPos = this.findLastToken(); michael@0: if (lastTokenPos != -1) { michael@0: var newValue = this.element.value.substr(0, lastTokenPos + 1); michael@0: var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/); michael@0: if (whitespace) { michael@0: newValue += whitespace[0]; michael@0: } michael@0: this.element.value = newValue + value; michael@0: } else { michael@0: this.element.value = value; michael@0: } michael@0: this.element.focus(); michael@0: michael@0: if (this.options.afterUpdateElement) { michael@0: this.options.afterUpdateElement(this.element, selectedElement); michael@0: } michael@0: }, michael@0: michael@0: /** @id Autocompleter.Base.prototype.updateChoices */ michael@0: updateChoices: function (choices) { michael@0: if (!this.changed && this.hasFocus) { michael@0: this.update.innerHTML = choices; michael@0: var d = MochiKit.DOM; michael@0: d.removeEmptyTextNodes(this.update); michael@0: d.removeEmptyTextNodes(this.update.firstChild); michael@0: michael@0: if (this.update.firstChild && this.update.firstChild.childNodes) { michael@0: this.entryCount = this.update.firstChild.childNodes.length; michael@0: for (var i = 0; i < this.entryCount; i++) { michael@0: var entry = this.getEntry(i); michael@0: entry.autocompleteIndex = i; michael@0: this.addObservers(entry); michael@0: } michael@0: } else { michael@0: this.entryCount = 0; michael@0: } michael@0: michael@0: this.stopIndicator(); michael@0: michael@0: this.index = 0; michael@0: this.render(); michael@0: } michael@0: }, michael@0: michael@0: /** @id Autocompleter.Base.prototype.addObservers */ michael@0: addObservers: function (element) { michael@0: MochiKit.Signal.connect(element, 'onmouseover', this, this.onHover); michael@0: MochiKit.Signal.connect(element, 'onclick', this, this.onClick); michael@0: }, michael@0: michael@0: /** @id Autocompleter.Base.prototype.onObserverEvent */ michael@0: onObserverEvent: function () { michael@0: this.changed = false; michael@0: if (this.getToken().length >= this.options.minChars) { michael@0: this.startIndicator(); michael@0: this.getUpdatedChoices(); michael@0: } else { michael@0: this.active = false; michael@0: this.hide(); michael@0: } michael@0: }, michael@0: michael@0: /** @id Autocompleter.Base.prototype.getToken */ michael@0: getToken: function () { michael@0: var tokenPos = this.findLastToken(); michael@0: if (tokenPos != -1) { michael@0: var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,''); michael@0: } else { michael@0: var ret = this.element.value; michael@0: } michael@0: return /\n/.test(ret) ? '' : ret; michael@0: }, michael@0: michael@0: /** @id Autocompleter.Base.prototype.findLastToken */ michael@0: findLastToken: function () { michael@0: var lastTokenPos = -1; michael@0: michael@0: for (var i = 0; i < this.options.tokens.length; i++) { michael@0: var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]); michael@0: if (thisTokenPos > lastTokenPos) { michael@0: lastTokenPos = thisTokenPos; michael@0: } michael@0: } michael@0: return lastTokenPos; michael@0: } michael@0: } michael@0: michael@0: /** @id Ajax.Autocompleter */ michael@0: Ajax.Autocompleter = function (element, update, url, options) { michael@0: this.__init__(element, update, url, options); michael@0: }; michael@0: michael@0: MochiKit.Base.update(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype); michael@0: michael@0: MochiKit.Base.update(Ajax.Autocompleter.prototype, { michael@0: __init__: function (element, update, url, options) { michael@0: this.baseInitialize(element, update, options); michael@0: this.options.asynchronous = true; michael@0: this.options.onComplete = MochiKit.Base.bind(this.onComplete, this); michael@0: this.options.defaultParams = this.options.parameters || null; michael@0: this.url = url; michael@0: }, michael@0: michael@0: /** @id Ajax.Autocompleter.prototype.getUpdatedChoices */ michael@0: getUpdatedChoices: function () { michael@0: var entry = encodeURIComponent(this.options.paramName) + '=' + michael@0: encodeURIComponent(this.getToken()); michael@0: michael@0: this.options.parameters = this.options.callback ? michael@0: this.options.callback(this.element, entry) : entry; michael@0: michael@0: if (this.options.defaultParams) { michael@0: this.options.parameters += '&' + this.options.defaultParams; michael@0: } michael@0: new Ajax.Request(this.url, this.options); michael@0: }, michael@0: michael@0: /** @id Ajax.Autocompleter.prototype.onComplete */ michael@0: onComplete: function (request) { michael@0: this.updateChoices(request.responseText); michael@0: } michael@0: }); michael@0: michael@0: /*** michael@0: michael@0: The local array autocompleter. Used when you'd prefer to michael@0: inject an array of autocompletion options into the page, rather michael@0: than sending out Ajax queries, which can be quite slow sometimes. michael@0: michael@0: The constructor takes four parameters. The first two are, as usual, michael@0: the id of the monitored textbox, and id of the autocompletion menu. michael@0: The third is the array you want to autocomplete from, and the fourth michael@0: is the options block. michael@0: michael@0: Extra local autocompletion options: michael@0: - choices - How many autocompletion choices to offer michael@0: michael@0: - partialSearch - If false, the autocompleter will match entered michael@0: text only at the beginning of strings in the michael@0: autocomplete array. Defaults to true, which will michael@0: match text at the beginning of any *word* in the michael@0: strings in the autocomplete array. If you want to michael@0: search anywhere in the string, additionally set michael@0: the option fullSearch to true (default: off). michael@0: michael@0: - fullSsearch - Search anywhere in autocomplete array strings. michael@0: michael@0: - partialChars - How many characters to enter before triggering michael@0: a partial match (unlike minChars, which defines michael@0: how many characters are required to do any match michael@0: at all). Defaults to 2. michael@0: michael@0: - ignoreCase - Whether to ignore case when autocompleting. michael@0: Defaults to true. michael@0: michael@0: It's possible to pass in a custom function as the 'selector' michael@0: option, if you prefer to write your own autocompletion logic. michael@0: In that case, the other options above will not apply unless michael@0: you support them. michael@0: michael@0: ***/ michael@0: michael@0: /** @id Autocompleter.Local */ michael@0: Autocompleter.Local = function (element, update, array, options) { michael@0: this.__init__(element, update, array, options); michael@0: }; michael@0: michael@0: MochiKit.Base.update(Autocompleter.Local.prototype, Autocompleter.Base.prototype); michael@0: michael@0: MochiKit.Base.update(Autocompleter.Local.prototype, { michael@0: __init__: function (element, update, array, options) { michael@0: this.baseInitialize(element, update, options); michael@0: this.options.array = array; michael@0: }, michael@0: michael@0: /** @id Autocompleter.Local.prototype.getUpdatedChoices */ michael@0: getUpdatedChoices: function () { michael@0: this.updateChoices(this.options.selector(this)); michael@0: }, michael@0: michael@0: /** @id Autocompleter.Local.prototype.setOptions */ michael@0: setOptions: function (options) { michael@0: this.options = MochiKit.Base.update({ michael@0: choices: 10, michael@0: partialSearch: true, michael@0: partialChars: 2, michael@0: ignoreCase: true, michael@0: fullSearch: false, michael@0: selector: function (instance) { michael@0: var ret = []; // Beginning matches michael@0: var partial = []; // Inside matches michael@0: var entry = instance.getToken(); michael@0: var count = 0; michael@0: michael@0: for (var i = 0; i < instance.options.array.length && michael@0: ret.length < instance.options.choices ; i++) { michael@0: michael@0: var elem = instance.options.array[i]; michael@0: var foundPos = instance.options.ignoreCase ? michael@0: elem.toLowerCase().indexOf(entry.toLowerCase()) : michael@0: elem.indexOf(entry); michael@0: michael@0: while (foundPos != -1) { michael@0: if (foundPos === 0 && elem.length != entry.length) { michael@0: ret.push('
  • ' + elem.substr(0, entry.length) + '' + michael@0: elem.substr(entry.length) + '
  • '); michael@0: break; michael@0: } else if (entry.length >= instance.options.partialChars && michael@0: instance.options.partialSearch && foundPos != -1) { michael@0: if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos - 1, 1))) { michael@0: partial.push('
  • ' + elem.substr(0, foundPos) + '' + michael@0: elem.substr(foundPos, entry.length) + '' + elem.substr( michael@0: foundPos + entry.length) + '
  • '); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: foundPos = instance.options.ignoreCase ? michael@0: elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : michael@0: elem.indexOf(entry, foundPos + 1); michael@0: michael@0: } michael@0: } michael@0: if (partial.length) { michael@0: ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)) michael@0: } michael@0: return ''; michael@0: } michael@0: }, options || {}); michael@0: } michael@0: }); michael@0: michael@0: /*** michael@0: michael@0: AJAX in-place editor michael@0: michael@0: see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor michael@0: michael@0: Use this if you notice weird scrolling problems on some browsers, michael@0: the DOM might be a bit confused when this gets called so do this michael@0: waits 1 ms (with setTimeout) until it does the activation michael@0: michael@0: ***/ michael@0: michael@0: /** @id Ajax.InPlaceEditor */ michael@0: Ajax.InPlaceEditor = function (element, url, options) { michael@0: this.__init__(element, url, options); michael@0: }; michael@0: michael@0: /** @id Ajax.InPlaceEditor.defaultHighlightColor */ michael@0: Ajax.InPlaceEditor.defaultHighlightColor = '#FFFF99'; michael@0: michael@0: Ajax.InPlaceEditor.prototype = { michael@0: __init__: function (element, url, options) { michael@0: this.url = url; michael@0: this.element = MochiKit.DOM.getElement(element); michael@0: michael@0: this.options = MochiKit.Base.update({ michael@0: okButton: true, michael@0: okText: 'ok', michael@0: cancelLink: true, michael@0: cancelText: 'cancel', michael@0: savingText: 'Saving...', michael@0: clickToEditText: 'Click to edit', michael@0: okText: 'ok', michael@0: rows: 1, michael@0: onComplete: function (transport, element) { michael@0: new MochiKit.Visual.Highlight(element, {startcolor: this.options.highlightcolor}); michael@0: }, michael@0: onFailure: function (transport) { michael@0: alert('Error communicating with the server: ' + MochiKit.Base.stripTags(transport.responseText)); michael@0: }, michael@0: callback: function (form) { michael@0: return MochiKit.DOM.formContents(form); michael@0: }, michael@0: handleLineBreaks: true, michael@0: loadingText: 'Loading...', michael@0: savingClassName: 'inplaceeditor-saving', michael@0: loadingClassName: 'inplaceeditor-loading', michael@0: formClassName: 'inplaceeditor-form', michael@0: highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor, michael@0: highlightendcolor: '#FFFFFF', michael@0: externalControl: null, michael@0: submitOnBlur: false, michael@0: ajaxOptions: {} michael@0: }, options || {}); michael@0: michael@0: if (!this.options.formId && this.element.id) { michael@0: this.options.formId = this.element.id + '-inplaceeditor'; michael@0: if (MochiKit.DOM.getElement(this.options.formId)) { michael@0: // there's already a form with that name, don't specify an id michael@0: this.options.formId = null; michael@0: } michael@0: } michael@0: michael@0: if (this.options.externalControl) { michael@0: this.options.externalControl = MochiKit.DOM.getElement(this.options.externalControl); michael@0: } michael@0: michael@0: this.originalBackground = MochiKit.Style.getStyle(this.element, 'background-color'); michael@0: if (!this.originalBackground) { michael@0: this.originalBackground = 'transparent'; michael@0: } michael@0: michael@0: this.element.title = this.options.clickToEditText; michael@0: michael@0: this.onclickListener = MochiKit.Signal.connect(this.element, 'onclick', this, this.enterEditMode); michael@0: this.mouseoverListener = MochiKit.Signal.connect(this.element, 'onmouseover', this, this.enterHover); michael@0: this.mouseoutListener = MochiKit.Signal.connect(this.element, 'onmouseout', this, this.leaveHover); michael@0: if (this.options.externalControl) { michael@0: this.onclickListenerExternal = MochiKit.Signal.connect(this.options.externalControl, michael@0: 'onclick', this, this.enterEditMode); michael@0: this.mouseoverListenerExternal = MochiKit.Signal.connect(this.options.externalControl, michael@0: 'onmouseover', this, this.enterHover); michael@0: this.mouseoutListenerExternal = MochiKit.Signal.connect(this.options.externalControl, michael@0: 'onmouseout', this, this.leaveHover); michael@0: } michael@0: }, michael@0: michael@0: /** @id Ajax.InPlaceEditor.prototype.enterEditMode */ michael@0: enterEditMode: function (evt) { michael@0: if (this.saving) { michael@0: return; michael@0: } michael@0: if (this.editing) { michael@0: return; michael@0: } michael@0: this.editing = true; michael@0: this.onEnterEditMode(); michael@0: if (this.options.externalControl) { michael@0: MochiKit.Style.hideElement(this.options.externalControl); michael@0: } michael@0: MochiKit.Style.hideElement(this.element); michael@0: this.createForm(); michael@0: this.element.parentNode.insertBefore(this.form, this.element); michael@0: Field.scrollFreeActivate(this.editField); michael@0: // stop the event to avoid a page refresh in Safari michael@0: if (evt) { michael@0: evt.stop(); michael@0: } michael@0: return false; michael@0: }, michael@0: michael@0: /** @id Ajax.InPlaceEditor.prototype.createForm */ michael@0: createForm: function () { michael@0: this.form = document.createElement('form'); michael@0: this.form.id = this.options.formId; michael@0: MochiKit.DOM.addElementClass(this.form, this.options.formClassName) michael@0: this.form.onsubmit = MochiKit.Base.bind(this.onSubmit, this); michael@0: michael@0: this.createEditField(); michael@0: michael@0: if (this.options.textarea) { michael@0: var br = document.createElement('br'); michael@0: this.form.appendChild(br); michael@0: } michael@0: michael@0: if (this.options.okButton) { michael@0: okButton = document.createElement('input'); michael@0: okButton.type = 'submit'; michael@0: okButton.value = this.options.okText; michael@0: this.form.appendChild(okButton); michael@0: } michael@0: michael@0: if (this.options.cancelLink) { michael@0: cancelLink = document.createElement('a'); michael@0: cancelLink.href = '#'; michael@0: cancelLink.appendChild(document.createTextNode(this.options.cancelText)); michael@0: cancelLink.onclick = MochiKit.Base.bind(this.onclickCancel, this); michael@0: this.form.appendChild(cancelLink); michael@0: } michael@0: }, michael@0: michael@0: /** @id Ajax.InPlaceEditor.prototype.hasHTMLLineBreaks */ michael@0: hasHTMLLineBreaks: function (string) { michael@0: if (!this.options.handleLineBreaks) { michael@0: return false; michael@0: } michael@0: return string.match(/
    /i); michael@0: }, michael@0: michael@0: /** @id Ajax.InPlaceEditor.prototype.convertHTMLLineBreaks */ michael@0: convertHTMLLineBreaks: function (string) { michael@0: return string.replace(/
    /gi, '\n').replace(//gi, '\n').replace(/<\/p>/gi, '\n').replace(/

    /gi, ''); michael@0: }, michael@0: michael@0: /** @id Ajax.InPlaceEditor.prototype.createEditField */ michael@0: createEditField: function () { michael@0: var text; michael@0: if (this.options.loadTextURL) { michael@0: text = this.options.loadingText; michael@0: } else { michael@0: text = this.getText(); michael@0: } michael@0: michael@0: var obj = this; michael@0: michael@0: if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) { michael@0: this.options.textarea = false; michael@0: var textField = document.createElement('input'); michael@0: textField.obj = this; michael@0: textField.type = 'text'; michael@0: textField.name = 'value'; michael@0: textField.value = text; michael@0: textField.style.backgroundColor = this.options.highlightcolor; michael@0: var size = this.options.size || this.options.cols || 0; michael@0: if (size !== 0) { michael@0: textField.size = size; michael@0: } michael@0: if (this.options.submitOnBlur) { michael@0: textField.onblur = MochiKit.Base.bind(this.onSubmit, this); michael@0: } michael@0: this.editField = textField; michael@0: } else { michael@0: this.options.textarea = true; michael@0: var textArea = document.createElement('textarea'); michael@0: textArea.obj = this; michael@0: textArea.name = 'value'; michael@0: textArea.value = this.convertHTMLLineBreaks(text); michael@0: textArea.rows = this.options.rows; michael@0: textArea.cols = this.options.cols || 40; michael@0: if (this.options.submitOnBlur) { michael@0: textArea.onblur = MochiKit.Base.bind(this.onSubmit, this); michael@0: } michael@0: this.editField = textArea; michael@0: } michael@0: michael@0: if (this.options.loadTextURL) { michael@0: this.loadExternalText(); michael@0: } michael@0: this.form.appendChild(this.editField); michael@0: }, michael@0: michael@0: /** @id Ajax.InPlaceEditor.prototype.getText */ michael@0: getText: function () { michael@0: return this.element.innerHTML; michael@0: }, michael@0: michael@0: /** @id Ajax.InPlaceEditor.prototype.loadExternalText */ michael@0: loadExternalText: function () { michael@0: MochiKit.DOM.addElementClass(this.form, this.options.loadingClassName); michael@0: this.editField.disabled = true; michael@0: new Ajax.Request( michael@0: this.options.loadTextURL, michael@0: MochiKit.Base.update({ michael@0: asynchronous: true, michael@0: onComplete: MochiKit.Base.bind(this.onLoadedExternalText, this) michael@0: }, this.options.ajaxOptions) michael@0: ); michael@0: }, michael@0: michael@0: /** @id Ajax.InPlaceEditor.prototype.onLoadedExternalText */ michael@0: onLoadedExternalText: function (transport) { michael@0: MochiKit.DOM.removeElementClass(this.form, this.options.loadingClassName); michael@0: this.editField.disabled = false; michael@0: this.editField.value = MochiKit.Base.stripTags(transport); michael@0: }, michael@0: michael@0: /** @id Ajax.InPlaceEditor.prototype.onclickCancel */ michael@0: onclickCancel: function () { michael@0: this.onComplete(); michael@0: this.leaveEditMode(); michael@0: return false; michael@0: }, michael@0: michael@0: /** @id Ajax.InPlaceEditor.prototype.onFailure */ michael@0: onFailure: function (transport) { michael@0: this.options.onFailure(transport); michael@0: if (this.oldInnerHTML) { michael@0: this.element.innerHTML = this.oldInnerHTML; michael@0: this.oldInnerHTML = null; michael@0: } michael@0: return false; michael@0: }, michael@0: michael@0: /** @id Ajax.InPlaceEditor.prototype.onSubmit */ michael@0: onSubmit: function () { michael@0: // onLoading resets these so we need to save them away for the Ajax call michael@0: var form = this.form; michael@0: var value = this.editField.value; michael@0: michael@0: // do this first, sometimes the ajax call returns before we get a michael@0: // chance to switch on Saving which means this will actually switch on michael@0: // Saving *after* we have left edit mode causing Saving to be michael@0: // displayed indefinitely michael@0: this.onLoading(); michael@0: michael@0: new Ajax.Updater( michael@0: { michael@0: success: this.element, michael@0: // dont update on failure (this could be an option) michael@0: failure: null michael@0: }, michael@0: this.url, michael@0: MochiKit.Base.update({ michael@0: parameters: this.options.callback(form, value), michael@0: onComplete: MochiKit.Base.bind(this.onComplete, this), michael@0: onFailure: MochiKit.Base.bind(this.onFailure, this) michael@0: }, this.options.ajaxOptions) michael@0: ); michael@0: // stop the event to avoid a page refresh in Safari michael@0: if (arguments.length > 1) { michael@0: arguments[0].stop(); michael@0: } michael@0: return false; michael@0: }, michael@0: michael@0: /** @id Ajax.InPlaceEditor.prototype.onLoading */ michael@0: onLoading: function () { michael@0: this.saving = true; michael@0: this.removeForm(); michael@0: this.leaveHover(); michael@0: this.showSaving(); michael@0: }, michael@0: michael@0: /** @id Ajax.InPlaceEditor.prototype.onSaving */ michael@0: showSaving: function () { michael@0: this.oldInnerHTML = this.element.innerHTML; michael@0: this.element.innerHTML = this.options.savingText; michael@0: MochiKit.DOM.addElementClass(this.element, this.options.savingClassName); michael@0: this.element.style.backgroundColor = this.originalBackground; michael@0: MochiKit.Style.showElement(this.element); michael@0: }, michael@0: michael@0: /** @id Ajax.InPlaceEditor.prototype.removeForm */ michael@0: removeForm: function () { michael@0: if (this.form) { michael@0: if (this.form.parentNode) { michael@0: MochiKit.DOM.removeElement(this.form); michael@0: } michael@0: this.form = null; michael@0: } michael@0: }, michael@0: michael@0: /** @id Ajax.InPlaceEditor.prototype.enterHover */ michael@0: enterHover: function () { michael@0: if (this.saving) { michael@0: return; michael@0: } michael@0: this.element.style.backgroundColor = this.options.highlightcolor; michael@0: if (this.effect) { michael@0: this.effect.cancel(); michael@0: } michael@0: MochiKit.DOM.addElementClass(this.element, this.options.hoverClassName) michael@0: }, michael@0: michael@0: /** @id Ajax.InPlaceEditor.prototype.leaveHover */ michael@0: leaveHover: function () { michael@0: if (this.options.backgroundColor) { michael@0: this.element.style.backgroundColor = this.oldBackground; michael@0: } michael@0: MochiKit.DOM.removeElementClass(this.element, this.options.hoverClassName) michael@0: if (this.saving) { michael@0: return; michael@0: } michael@0: this.effect = new MochiKit.Visual.Highlight(this.element, { michael@0: startcolor: this.options.highlightcolor, michael@0: endcolor: this.options.highlightendcolor, michael@0: restorecolor: this.originalBackground michael@0: }); michael@0: }, michael@0: michael@0: /** @id Ajax.InPlaceEditor.prototype.leaveEditMode */ michael@0: leaveEditMode: function () { michael@0: MochiKit.DOM.removeElementClass(this.element, this.options.savingClassName); michael@0: this.removeForm(); michael@0: this.leaveHover(); michael@0: this.element.style.backgroundColor = this.originalBackground; michael@0: MochiKit.Style.showElement(this.element); michael@0: if (this.options.externalControl) { michael@0: MochiKit.Style.showElement(this.options.externalControl); michael@0: } michael@0: this.editing = false; michael@0: this.saving = false; michael@0: this.oldInnerHTML = null; michael@0: this.onLeaveEditMode(); michael@0: }, michael@0: michael@0: /** @id Ajax.InPlaceEditor.prototype.onComplete */ michael@0: onComplete: function (transport) { michael@0: this.leaveEditMode(); michael@0: MochiKit.Base.bind(this.options.onComplete, this)(transport, this.element); michael@0: }, michael@0: michael@0: /** @id Ajax.InPlaceEditor.prototype.onEnterEditMode */ michael@0: onEnterEditMode: function () {}, michael@0: michael@0: /** @id Ajax.InPlaceEditor.prototype.onLeaveEditMode */ michael@0: onLeaveEditMode: function () {}, michael@0: michael@0: /** @id Ajax.InPlaceEditor.prototype.dispose */ michael@0: dispose: function () { michael@0: if (this.oldInnerHTML) { michael@0: this.element.innerHTML = this.oldInnerHTML; michael@0: } michael@0: this.leaveEditMode(); michael@0: MochiKit.Signal.disconnect(this.onclickListener); michael@0: MochiKit.Signal.disconnect(this.mouseoverListener); michael@0: MochiKit.Signal.disconnect(this.mouseoutListener); michael@0: if (this.options.externalControl) { michael@0: MochiKit.Signal.disconnect(this.onclickListenerExternal); michael@0: MochiKit.Signal.disconnect(this.mouseoverListenerExternal); michael@0: MochiKit.Signal.disconnect(this.mouseoutListenerExternal); michael@0: } michael@0: } michael@0: }; michael@0: