lastTokenPos)
michael@0: lastTokenPos = thisTokenPos;
michael@0: }
michael@0: return lastTokenPos;
michael@0: }
michael@0: }
michael@0:
michael@0: Ajax.Autocompleter = Class.create();
michael@0: Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
michael@0: initialize: function(element, update, url, options) {
michael@0: this.baseInitialize(element, update, options);
michael@0: this.options.asynchronous = true;
michael@0: this.options.onComplete = this.onComplete.bind(this);
michael@0: this.options.defaultParams = this.options.parameters || null;
michael@0: this.url = url;
michael@0: },
michael@0:
michael@0: getUpdatedChoices: function() {
michael@0: this.startIndicator();
michael@0:
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: onComplete: function(request) {
michael@0: this.updateChoices(request.responseText);
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: Autocompleter.Local = Class.create();
michael@0: Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
michael@0: initialize: function(element, update, array, options) {
michael@0: this.baseInitialize(element, update, options);
michael@0: this.options.array = array;
michael@0: },
michael@0:
michael@0: getUpdatedChoices: function() {
michael@0: this.updateChoices(this.options.selector(this));
michael@0: },
michael@0:
michael@0: setOptions: function(options) {
michael@0: this.options = Object.extend({
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: return "";
michael@0: }
michael@0: }, options || {});
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: Field.scrollFreeActivate = function(field) {
michael@0: setTimeout(function() {
michael@0: Field.activate(field);
michael@0: }, 1);
michael@0: }
michael@0:
michael@0: Ajax.InPlaceEditor = Class.create();
michael@0: Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99";
michael@0: Ajax.InPlaceEditor.prototype = {
michael@0: initialize: function(element, url, options) {
michael@0: this.url = url;
michael@0: this.element = $(element);
michael@0:
michael@0: this.options = Object.extend({
michael@0: paramName: "value",
michael@0: okButton: true,
michael@0: okLink: false,
michael@0: okText: "ok",
michael@0: cancelButton: false,
michael@0: cancelLink: true,
michael@0: cancelText: "cancel",
michael@0: textBeforeControls: '',
michael@0: textBetweenControls: '',
michael@0: textAfterControls: '',
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 Effect.Highlight(element, {startcolor: this.options.highlightcolor});
michael@0: },
michael@0: onFailure: function(transport) {
michael@0: alert("Error communicating with the server: " + transport.responseText.stripTags());
michael@0: },
michael@0: callback: function(form) {
michael@0: return Form.serialize(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: evalScripts: false
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 ($(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 = $(this.options.externalControl);
michael@0: }
michael@0:
michael@0: this.originalBackground = Element.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 = this.enterEditMode.bindAsEventListener(this);
michael@0: this.mouseoverListener = this.enterHover.bindAsEventListener(this);
michael@0: this.mouseoutListener = this.leaveHover.bindAsEventListener(this);
michael@0: Event.observe(this.element, 'click', this.onclickListener);
michael@0: Event.observe(this.element, 'mouseover', this.mouseoverListener);
michael@0: Event.observe(this.element, 'mouseout', this.mouseoutListener);
michael@0: if (this.options.externalControl) {
michael@0: Event.observe(this.options.externalControl, 'click', this.onclickListener);
michael@0: Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener);
michael@0: Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
michael@0: }
michael@0: },
michael@0: enterEditMode: function(evt) {
michael@0: if (this.saving) return;
michael@0: if (this.editing) return;
michael@0: this.editing = true;
michael@0: this.onEnterEditMode();
michael@0: if (this.options.externalControl) {
michael@0: Element.hide(this.options.externalControl);
michael@0: }
michael@0: Element.hide(this.element);
michael@0: this.createForm();
michael@0: this.element.parentNode.insertBefore(this.form, this.element);
michael@0: if (!this.options.loadTextURL) Field.scrollFreeActivate(this.editField);
michael@0: // stop the event to avoid a page refresh in Safari
michael@0: if (evt) {
michael@0: Event.stop(evt);
michael@0: }
michael@0: return false;
michael@0: },
michael@0: createForm: function() {
michael@0: this.form = document.createElement("form");
michael@0: this.form.id = this.options.formId;
michael@0: Element.addClassName(this.form, this.options.formClassName)
michael@0: this.form.onsubmit = this.onSubmit.bind(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.textBeforeControls)
michael@0: this.form.appendChild(document.createTextNode(this.options.textBeforeControls));
michael@0:
michael@0: if (this.options.okButton) {
michael@0: var okButton = document.createElement("input");
michael@0: okButton.type = "submit";
michael@0: okButton.value = this.options.okText;
michael@0: okButton.className = 'editor_ok_button';
michael@0: this.form.appendChild(okButton);
michael@0: }
michael@0:
michael@0: if (this.options.okLink) {
michael@0: var okLink = document.createElement("a");
michael@0: okLink.href = "#";
michael@0: okLink.appendChild(document.createTextNode(this.options.okText));
michael@0: okLink.onclick = this.onSubmit.bind(this);
michael@0: okLink.className = 'editor_ok_link';
michael@0: this.form.appendChild(okLink);
michael@0: }
michael@0:
michael@0: if (this.options.textBetweenControls &&
michael@0: (this.options.okLink || this.options.okButton) &&
michael@0: (this.options.cancelLink || this.options.cancelButton))
michael@0: this.form.appendChild(document.createTextNode(this.options.textBetweenControls));
michael@0:
michael@0: if (this.options.cancelButton) {
michael@0: var cancelButton = document.createElement("input");
michael@0: cancelButton.type = "submit";
michael@0: cancelButton.value = this.options.cancelText;
michael@0: cancelButton.onclick = this.onclickCancel.bind(this);
michael@0: cancelButton.className = 'editor_cancel_button';
michael@0: this.form.appendChild(cancelButton);
michael@0: }
michael@0:
michael@0: if (this.options.cancelLink) {
michael@0: var cancelLink = document.createElement("a");
michael@0: cancelLink.href = "#";
michael@0: cancelLink.appendChild(document.createTextNode(this.options.cancelText));
michael@0: cancelLink.onclick = this.onclickCancel.bind(this);
michael@0: cancelLink.className = 'editor_cancel editor_cancel_link';
michael@0: this.form.appendChild(cancelLink);
michael@0: }
michael@0:
michael@0: if (this.options.textAfterControls)
michael@0: this.form.appendChild(document.createTextNode(this.options.textAfterControls));
michael@0: },
michael@0: hasHTMLLineBreaks: function(string) {
michael@0: if (!this.options.handleLineBreaks) return false;
michael@0: return string.match(/
/i);
michael@0: },
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: 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 = this.options.paramName;
michael@0: textField.value = text;
michael@0: textField.style.backgroundColor = this.options.highlightcolor;
michael@0: textField.className = 'editor_field';
michael@0: var size = this.options.size || this.options.cols || 0;
michael@0: if (size != 0) textField.size = size;
michael@0: if (this.options.submitOnBlur)
michael@0: textField.onblur = this.onSubmit.bind(this);
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 = this.options.paramName;
michael@0: textArea.value = this.convertHTMLLineBreaks(text);
michael@0: textArea.rows = this.options.rows;
michael@0: textArea.cols = this.options.cols || 40;
michael@0: textArea.className = 'editor_field';
michael@0: if (this.options.submitOnBlur)
michael@0: textArea.onblur = this.onSubmit.bind(this);
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: getText: function() {
michael@0: return this.element.innerHTML;
michael@0: },
michael@0: loadExternalText: function() {
michael@0: Element.addClassName(this.form, this.options.loadingClassName);
michael@0: this.editField.disabled = true;
michael@0: new Ajax.Request(
michael@0: this.options.loadTextURL,
michael@0: Object.extend({
michael@0: asynchronous: true,
michael@0: onComplete: this.onLoadedExternalText.bind(this)
michael@0: }, this.options.ajaxOptions)
michael@0: );
michael@0: },
michael@0: onLoadedExternalText: function(transport) {
michael@0: Element.removeClassName(this.form, this.options.loadingClassName);
michael@0: this.editField.disabled = false;
michael@0: this.editField.value = transport.responseText.stripTags();
michael@0: Field.scrollFreeActivate(this.editField);
michael@0: },
michael@0: onclickCancel: function() {
michael@0: this.onComplete();
michael@0: this.leaveEditMode();
michael@0: return false;
michael@0: },
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: 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 chance to switch on Saving...
michael@0: // which means this will actually switch on Saving... *after* we've left edit mode causing Saving...
michael@0: // to be displayed indefinitely
michael@0: this.onLoading();
michael@0:
michael@0: if (this.options.evalScripts) {
michael@0: new Ajax.Request(
michael@0: this.url, Object.extend({
michael@0: parameters: this.options.callback(form, value),
michael@0: onComplete: this.onComplete.bind(this),
michael@0: onFailure: this.onFailure.bind(this),
michael@0: asynchronous:true,
michael@0: evalScripts:true
michael@0: }, this.options.ajaxOptions));
michael@0: } else {
michael@0: new Ajax.Updater(
michael@0: { success: this.element,
michael@0: // don't update on failure (this could be an option)
michael@0: failure: null },
michael@0: this.url, Object.extend({
michael@0: parameters: this.options.callback(form, value),
michael@0: onComplete: this.onComplete.bind(this),
michael@0: onFailure: this.onFailure.bind(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: Event.stop(arguments[0]);
michael@0: }
michael@0: return false;
michael@0: },
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: showSaving: function() {
michael@0: this.oldInnerHTML = this.element.innerHTML;
michael@0: this.element.innerHTML = this.options.savingText;
michael@0: Element.addClassName(this.element, this.options.savingClassName);
michael@0: this.element.style.backgroundColor = this.originalBackground;
michael@0: Element.show(this.element);
michael@0: },
michael@0: removeForm: function() {
michael@0: if(this.form) {
michael@0: if (this.form.parentNode) Element.remove(this.form);
michael@0: this.form = null;
michael@0: }
michael@0: },
michael@0: enterHover: function() {
michael@0: if (this.saving) return;
michael@0: this.element.style.backgroundColor = this.options.highlightcolor;
michael@0: if (this.effect) {
michael@0: this.effect.cancel();
michael@0: }
michael@0: Element.addClassName(this.element, this.options.hoverClassName)
michael@0: },
michael@0: leaveHover: function() {
michael@0: if (this.options.backgroundColor) {
michael@0: this.element.style.backgroundColor = this.oldBackground;
michael@0: }
michael@0: Element.removeClassName(this.element, this.options.hoverClassName)
michael@0: if (this.saving) return;
michael@0: this.effect = new Effect.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: leaveEditMode: function() {
michael@0: Element.removeClassName(this.element, this.options.savingClassName);
michael@0: this.removeForm();
michael@0: this.leaveHover();
michael@0: this.element.style.backgroundColor = this.originalBackground;
michael@0: Element.show(this.element);
michael@0: if (this.options.externalControl) {
michael@0: Element.show(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: onComplete: function(transport) {
michael@0: this.leaveEditMode();
michael@0: this.options.onComplete.bind(this)(transport, this.element);
michael@0: },
michael@0: onEnterEditMode: function() {},
michael@0: onLeaveEditMode: function() {},
michael@0: dispose: function() {
michael@0: if (this.oldInnerHTML) {
michael@0: this.element.innerHTML = this.oldInnerHTML;
michael@0: }
michael@0: this.leaveEditMode();
michael@0: Event.stopObserving(this.element, 'click', this.onclickListener);
michael@0: Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
michael@0: Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
michael@0: if (this.options.externalControl) {
michael@0: Event.stopObserving(this.options.externalControl, 'click', this.onclickListener);
michael@0: Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener);
michael@0: Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener);
michael@0: }
michael@0: }
michael@0: };
michael@0:
michael@0: Ajax.InPlaceCollectionEditor = Class.create();
michael@0: Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype);
michael@0: Object.extend(Ajax.InPlaceCollectionEditor.prototype, {
michael@0: createEditField: function() {
michael@0: if (!this.cached_selectTag) {
michael@0: var selectTag = document.createElement("select");
michael@0: var collection = this.options.collection || [];
michael@0: var optionTag;
michael@0: collection.each(function(e,i) {
michael@0: optionTag = document.createElement("option");
michael@0: optionTag.value = (e instanceof Array) ? e[0] : e;
michael@0: if((typeof this.options.value == 'undefined') &&
michael@0: ((e instanceof Array) ? this.element.innerHTML == e[1] : e == optionTag.value)) optionTag.selected = true;
michael@0: if(this.options.value==optionTag.value) optionTag.selected = true;
michael@0: optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e));
michael@0: selectTag.appendChild(optionTag);
michael@0: }.bind(this));
michael@0: this.cached_selectTag = selectTag;
michael@0: }
michael@0:
michael@0: this.editField = this.cached_selectTag;
michael@0: if(this.options.loadTextURL) this.loadExternalText();
michael@0: this.form.appendChild(this.editField);
michael@0: this.options.callback = function(form, value) {
michael@0: return "value=" + encodeURIComponent(value);
michael@0: }
michael@0: }
michael@0: });
michael@0:
michael@0: // Delayed observer, like Form.Element.Observer,
michael@0: // but waits for delay after last key input
michael@0: // Ideal for live-search fields
michael@0:
michael@0: Form.Element.DelayedObserver = Class.create();
michael@0: Form.Element.DelayedObserver.prototype = {
michael@0: initialize: function(element, delay, callback) {
michael@0: this.delay = delay || 0.5;
michael@0: this.element = $(element);
michael@0: this.callback = callback;
michael@0: this.timer = null;
michael@0: this.lastValue = $F(this.element);
michael@0: Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
michael@0: },
michael@0: delayedListener: function(event) {
michael@0: if(this.lastValue == $F(this.element)) return;
michael@0: if(this.timer) clearTimeout(this.timer);
michael@0: this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
michael@0: this.lastValue = $F(this.element);
michael@0: },
michael@0: onTimerEvent: function() {
michael@0: this.timer = null;
michael@0: this.callback(this.element, $F(this.element));
michael@0: }
michael@0: };