Autocompleter.MultiSelectable = {}; Autocompleter.MultiSelectable.Base = Autocompleter.Base.extend({ options: { multiSelect: true, wrapSelectionsWithSpacesInQuotes: true }, choiceSelect: function(el) { //when multiSelect is enabled, append to field value instead of overwriting it. this.observer.value = this.element.value = this.options.multiSelect? this.element.value.trimLastElement() + el.inputValue + " ":el.inputValue; this.hideChoices(); this.fireEvent('onSelect', [this.element], 20); }, prefetch: function() { //when multiSelect is enabled, min len test on last query element so that min len is tested for every elements. var elValueToTest = this.options.multiSelect? this.element.value.lastElement(): this.element.value; if (elValueToTest.length < this.options.minLength) this.hideChoices(); else if (elValueToTest == this.queryValue) this.showChoices(); else this.query(); }, onCommand: function(e, mouse) { if (mouse && this.focussed) this.prefetch(); if (e.key && !e.shift) switch (e.key) { case 'enter': if (this.selected && this.visible) { this.choiceSelect(this.selected); e.stop(); } return; case 'up': case 'down': //when in multiselect mode, test on last element of element (observer) value or it will not //listen to key up and down. var elValueToTest = this.options.multiSelect? this.observer.value.lastElement(): this.observer.value; if (elValueToTest != (this.value || this.queryValue)) this.prefetch(); else if (this.queryValue === null) break; else if (!this.visible) this.showChoices(); else { this.choiceOver((e.key == 'up') ? this.selected.getPrevious() || this.choices.getLast() : this.selected.getNext() || this.choices.getFirst() ); this.setSelection(); } e.stop(); return; case 'esc': this.hideChoices(); return; } this.value = false; }, updateChoices: function(choices) { this.choices.empty(); this.selected = null; if (!choices || !choices.length) return; if (this.options.maxChoices < choices.length) choices.length = this.options.maxChoices; choices.each(this.options.injectChoice || function(choice, i){ var el = new Element('li').setHTML(this.markQueryValue(choice)); //wrapping in quotes if requested/needed. el.inputValue = this.options.wrapSelectionsWithSpacesInQuotes? choice.wrapInQuotes(): choice; this.addChoiceEvents(el).injectInside(this.choices); }, this); this.showChoices(); } }); Autocompleter.MultiSelectable.Ajax = {}; Autocompleter.MultiSelectable.Ajax.Base = Autocompleter.MultiSelectable.Base.extend({ // duplicated code (same as Autocompleter.Ajax.Base) options: { postVar: 'value', postData: {}, ajaxOptions: {}, onRequest: Class.empty, onComplete: Class.empty }, // duplicated code (same as Autocompleter.Ajax.Base) initialize: function(el, url, options) { this.parent(el, options); this.ajax = new Ajax(url, $merge({ autoCancel: true }, this.options.ajaxOptions)); this.ajax.addEvent('onComplete', this.queryResponse.bind(this)); this.ajax.addEvent('onFailure', this.queryResponse.bind(this, [false])); }, query: function(){ var data = $extend({}, this.options.postData); //query only on last element if multiSelectable is enabled. data[this.options.postVar] = this.options.multiSelect? this.element.value.lastElement(): this.element.value; this.fireEvent('onRequest', [this.element, this.ajax]); this.ajax.request(data); }, queryResponse: function(resp) { //query only on last element if multiSelectable is enabled. this.value = this.queryValue = this.options.multiSelect? this.element.value.lastElement(): this.element.value; this.selected = false; this.hideChoices(); this.fireEvent(resp ? 'onComplete' : 'onFailure', [this.element, this.ajax], 20); } }); //ready to use extension for JSON queries (identical to Autocompleter.Ajax.Json) Autocompleter.MultiSelectable.Ajax.Json = Autocompleter.MultiSelectable.Ajax.Base.extend({ queryResponse: function(resp) { this.parent(resp); var choices = Json.evaluate(resp || false); if (!choices || !choices.length) return; this.updateChoices(choices); } }); //ready to use extension for Local Array queries (identical to Autocompleter.Local) Autocompleter.MultiSelectable.Local = Autocompleter.MultiSelectable.Base.extend({ initialize: function(el, tokens, options) { this.parent(el, options); this.tokens = tokens; if (this.options.filterTokens) this.filterTokens = this.options.filterTokens.bind(this); }, query: function() { this.hideChoices(); this.queryValue = this.element.value.lastElement(); this.updateChoices(this.filterTokens()); }, filterTokens: function(token) { var regex = new RegExp('^' + this.queryValue.escapeRegExp(), 'i'); return this.tokens.filter(function(token) { return regex.test(token); }); } }); String.extend({ lastElement: function(separator){ var txt = this.trim(); var index = txt.lastIndexOf(separator || ' '); return (index == -1)? txt: txt.substr(index + 1, txt.length); },//end lastElement trimLastElement: function(separator){ var txt = this.trim(); var index = txt.lastIndexOf(separator || ' '); return (index == -1)? "": txt.substr(0, index + 1); },//end trimLastElement wrapInQuotes: function(){ var index = this.trim().lastIndexOf(' '); return (index == -1)? this: '"' + this + '"' }//end wrapInQuotes }); //end String.extend