/* Proto!MultiSelect Copyright: InteRiders - Distributed under MIT - Keep this message! */ // Added key contstant for COMMA watching happiness Object.extend(Event, { KEY_COMMA: 188, KEY_SPACE: 32 }); var ResizableTextbox = Class.create({ initialize: function(element, options) { var that = this; this.options = $H({ min: 5, max: 500, step: 7 }); this.options.update(options); this.el = $(element); this.width = this.el.offsetWidth; this.el.observe( 'keyup', function() { var newsize = that.options.get('step') * $F(this).length; if(newsize <= that.options.get('min')) newsize = that.options.get('mix'); if(! ($F(this).length == this.retrieveData('rt-value') || newsize <= that.options.min || newsize >= that.options.max)) this.setStyle({'width': newsize}); }).observe('keydown', function() { this.cacheData('rt-value', $F(this).length); } ); } }); var TextboxList = Class.create({ initialize: function(element, options) { this.options = $H({/* onFocus: $empty, onBlur: $empty, onInputFocus: $empty, onInputBlur: $empty, onBoxFocus: $empty, onBoxBlur: $empty, onBoxDispose: $empty,*/ resizable: {}, className: 'bit', separator: ',', separatorKeyCode: Event.KEY_COMMA, extrainputs: true, startinput: true, hideempty: true, newValues: false, newValueDelimiters: ['[',']'], spaceReplace: '', fetchFile: undefined, fetchMethod: 'get', results: 10, maxResults: 0, // 0 = set to default (which is 10 (see FacebookList class)), wordMatch: false, onEmptyInput: function(input){}, caseSensitive: false, regexSearch: true }); this.current_input = ""; this.options.update(options); this.element = $(element).hide(); this.bits = new Hash(); this.events = new Hash(); this.count = 0; this.current = false; this.maininput = this.createInput({'class': 'maininput'}); this.holder = new Element('ul', { 'class': 'holder' }).insert(this.maininput); this.element.insert({'before':this.holder}); this.holder.observe('click', function(event){ event.stop(); if(this.maininput != this.current) this.focus(this.maininput); }.bind(this)); this.makeResizable(this.maininput); this.setEvents(); }, setEvents: function() { document.observe(Prototype.Browser.IE ? 'keydown' : 'keypress', function(e) { if(! this.current) return; if(this.current.retrieveData('type') == 'box' && e.keyCode == Event.KEY_BACKSPACE) e.stop(); }.bind(this)); document.observe( 'keyup', function(e) { e.stop(); if(! this.current) return; switch(e.keyCode){ case Event.KEY_LEFT: return this.move('left'); case Event.KEY_RIGHT: return this.move('right'); case Event.KEY_DELETE: case Event.KEY_BACKSPACE: return this.moveDispose(); } }.bind(this)).observe( 'click', function() { document.fire('blur'); }.bindAsEventListener(this) ); }, update: function() { this.element.value = this.bits.values().join(this.options.get('separator')); if (!this.current_input.blank()){ this.element.value += (this.element.value.blank() ? "" : this.options.get('separator')) + this.current_input; } return this; }, add: function(text, html) { var id = this.id_base + '-' + this.count++; var el = this.createBox($pick(html, text), {'id': id, 'class': this.options.get('className'), 'newValue' : text.newValue ? 'true' : 'false'}); (this.current || this.maininput).insert({'before':el}); el.observe('click', function(e) { e.stop(); this.focus(el); }.bind(this)); this.bits.set(id, text.value); // Dynamic updating... why not? this.update(); if(this.options.get('extrainputs') && (this.options.get('startinput') || el.previous())) this.addSmallInput(el,'before'); return el; }, addSmallInput: function(el, where) { var input = this.createInput({'class': 'smallinput'}); el.insert({}[where] = input); input.cacheData('small', true); this.makeResizable(input); if(this.options.get('hideempty')) input.hide(); return input; }, dispose: function(el) { this.bits.unset(el.id); // Dynamic updating... why not? this.update(); if(el.previous() && el.previous().retrieveData('small')) el.previous().remove(); if(this.current == el) this.focus(el.next()); if(el.retrieveData('type') == 'box') el.onBoxDispose(this); el.remove(); return this; }, focus: function(el, nofocus) { if(! this.current) el.fire('focus'); else if(this.current == el) return this; this.blur(); el.addClassName(this.options.get('className') + '-' + el.retrieveData('type') + '-focus'); if(el.retrieveData('small')) el.setStyle({'display': 'block'}); if(el.retrieveData('type') == 'input') { el.onInputFocus(this); if(! nofocus) this.callEvent(el.retrieveData('input'), 'focus'); } else el.fire('onBoxFocus'); this.current = el; return this; }, blur: function(noblur) { if(! this.current) return this; if(this.current.retrieveData('type') == 'input') { var input = this.current.retrieveData('input'); if(! noblur) this.callEvent(input, 'blur'); input.onInputBlur(this); } else this.current.fire('onBoxBlur'); if(this.current.retrieveData('small') && ! input.get('value') && this.options.get('hideempty')) this.current.hide(); this.current.removeClassName(this.options.get('className') + '-' + this.current.retrieveData('type') + '-focus'); this.current = false; return this; }, createBox: function(text, options) { return new Element('li', options).addClassName(this.options.get('className') + '-box').update(text.caption).cacheData('type', 'box'); }, createInput: function(options) { var li = new Element('li', {'class': this.options.get('className') + '-input'}); var el = new Element('input', Object.extend(options,{'type': 'text', 'autocomplete':'off'})); el.observe('click', function(e) { e.stop(); }).observe('focus', function(e) { if(! this.isSelfEvent('focus')) this.focus(li, true); }.bind(this)).observe('blur', function() { if(! this.isSelfEvent('blur')) this.blur(true); }.bind(this)).observe('keydown', function(e) { this.cacheData('lastvalue', this.value).cacheData('lastcaret', this.getCaretPosition()); }); var tmp = li.cacheData('type', 'input').cacheData('input', el).insert(el); return tmp; }, callEvent: function(el, type) { this.events.set(type, el); el[type](); }, isSelfEvent: function(type) { return (this.events.get(type)) ? !! this.events.unset(type) : false; }, makeResizable: function(li) { var el = li.retrieveData('input'); el.cacheData('resizable', new ResizableTextbox(el, Object.extend(this.options.get('resizable'),{min: 150, max: 500}))); return this; }, checkInput: function() { var input = this.current.retrieveData('input'); return (! input.retrieveData('lastvalue') || (input.getCaretPosition() === 0 && input.retrieveData('lastcaret') === 0)); }, move: function(direction) { var el = this.current[(direction == 'left' ? 'previous' : 'next')](); if(el && (! this.current.retrieveData('input') || ((this.checkInput() || direction == 'right')))) this.focus(el); return this; }, moveDispose: function() { if(this.current.retrieveData('type') == 'box') return this.dispose(this.current); if(this.checkInput() && this.bits.keys().length && this.current.previous()) return this.focus(this.current.previous()); } }); //helper functions Element.addMethods({ getCaretPosition: function() { if (this.createTextRange) { var r = document.selection.createRange().duplicate(); r.moveEnd('character', this.value.length); if (r.text === '') return this.value.length; return this.value.lastIndexOf(r.text); } else return this.selectionStart; }, cacheData: function(element, key, value) { if (Object.isUndefined(this[$(element).identify()]) || !Object.isHash(this[$(element).identify()])) this[$(element).identify()] = $H(); this[$(element).identify()].set(key,value); return element; }, retrieveData: function(element,key) { return this[$(element).identify()].get(key); } }); function $pick(){for(var B=0,A=arguments.length;B= 0) { matches[matches_found++] = this.data[i]; } } } } else { if (this.options.get('wordMatch')) { var regexp = new RegExp("(^|\\s)"+search,(!this.options.get('caseSensitive') ? 'i' : '')); } else { var regexp = new RegExp(search,(!this.options.get('caseSensitive') ? 'i' : '')); var matches = this.data.filter( function(str) { return str ? regexp.test(str.evalJSON(true).caption) : false; }); } } var count = 0; matches.each( function(result, ti) { count++; if(ti >= (this.options.get('maxResults') ? this.options.get('maxResults') : this.loptions.get('autocomplete').maxresults)) return; var that = this; var el = new Element('li'); el.observe('click',function(e) { e.stop(); that.current_input = ""; that.autoAdd(this); } ).observe('mouseover', function() { that.autoFocus(this); } ).update( this.autoHighlight(result.evalJSON(true).caption, search) ); this.autoresults.insert(el); el.cacheData('result', result.evalJSON(true)); if(ti == 0) this.autoFocus(el); }, this ); } if (count == 0) { // if there are no results, hide everything so that KEY_ENTER has no effect this.autoHide(); } else { if (count > this.options.get('results')) this.autoresults.setStyle({'height': (this.options.get('results')*24)+'px'}); else this.autoresults.setStyle({'height': (count?(count*24):0)+'px'}); } return this; }, autoHighlight: function(html, highlight) { return html.gsub(new RegExp(highlight,'i'), function(match) { return '' + match[0] + ''; }); }, autoHide: function() { this.resultsshown = false; this.autoholder.hide(); return this; }, autoFocus: function(el) { if(! el) return; if(this.autocurrent) this.autocurrent.removeClassName('auto-focus'); this.autocurrent = el.addClassName('auto-focus'); return this; }, autoMove: function(direction) { if(!this.resultsshown) return; this.autoFocus(this.autocurrent[(direction == 'up' ? 'previous' : 'next')]()); this.autoresults.scrollTop = this.autocurrent.positionedOffset()[1]-this.autocurrent.getHeight(); return this; }, autoFeed: function(text) { var with_case = this.options.get('caseSensitive'); if (this.data.indexOf(Object.toJSON(text)) == -1) { this.data.push(Object.toJSON(text)); this.data_searchable.push(with_case ? Object.toJSON(text).evalJSON(true).caption : Object.toJSON(text).evalJSON(true).caption.toLowerCase()); } return this; }, autoAdd: function(el) { if(this.newvalue && this.options.get("newValues")) { this.add({caption: el.value, value: el.value, newValue: true}); var input = el; } else if(!el || ! el.retrieveData('result')) { return; } else { this.add(el.retrieveData('result')); delete this.data[this.data.indexOf(Object.toJSON(el.retrieveData('result')))]; var input = this.lastinput || this.current.retrieveData('input'); } this.autoHide(); input.clear().focus(); return this; }, createInput: function($super,options) { var li = $super(options); var input = li.retrieveData('input'); input.observe('keydown', function(e) { this.dosearch = false; this.newvalue = false; switch(e.keyCode) { case Event.KEY_UP: e.stop(); return this.autoMove('up'); case Event.KEY_DOWN: e.stop(); return this.autoMove('down'); case Event.KEY_RETURN: // If the text input is blank and the user hits Enter call the // onEmptyInput callback. if (String('').valueOf() == String(this.current.retrieveData('input').getValue()).valueOf()) { this.options.get("onEmptyInput")(); } e.stop(); if(!this.autocurrent || !this.resultsshown) break; this.current_input = ""; this.autoAdd(this.autocurrent); this.autocurrent = false; this.autoenter = true; break; case Event.KEY_ESC: this.autoHide(); if(this.current && this.current.retrieveData('input')) this.current.retrieveData('input').clear(); break; default: this.dosearch = true; } }.bind(this)); input.observe('keyup',function(e) { switch(e.keyCode) { case this.options.get('separatorKeyCode'): if(this.options.get('newValues')) { var separator = this.options.get('separator'); new_value_el = this.current.retrieveData('input'); if (!new_value_el.value.endsWith('<')) { keep_input = ""; new_value_el.value = new_value_el.value.strip(); if (new_value_el.value.indexOf(separator) < (new_value_el.value.length - 1)){ comma_pos = new_value_el.value.indexOf(separator); keep_input = new_value_el.value.substr(comma_pos + 1); new_value_el.value = new_value_el.value.substr(0,comma_pos).escapeHTML().strip(); } else { new_value_el.value = new_value_el.value.gsub(separator,"").escapeHTML().strip(); } if(!this.options.get("spaceReplace").blank()) new_value_el.value.gsub(" ", this.options.get("spaceReplace")); if(!new_value_el.value.blank()) { e.stop(); this.newvalue = true; this.current_input = keep_input.escapeHTML().strip(); this.autoAdd(new_value_el); input.value = keep_input; this.update(); } } } break; case Event.KEY_UP: case Event.KEY_DOWN: case Event.KEY_RETURN: case Event.KEY_ESC: break; default: // If the user doesn't add comma after, the value is discarded upon submit this.current_input = input.value.strip().escapeHTML(); this.update(); // Removed Ajax.Request from here and moved to initialize, // now doesn't create server queries every search but only // refreshes the list on initialize (page load) if(this.searchTimeout) clearTimeout(this.searchTimeout); this.searchTimeout = setTimeout(function(){ var sanitizer = new RegExp("[({[^$*+?\\\]})]","g"); if(this.dosearch) this.autoShow(input.value.replace(sanitizer,"\\$1")); }.bind(this), 250); } }.bind(this)); input.observe(Prototype.Browser.IE ? 'keydown' : 'keypress', function(e) { if ((e.keyCode == Event.KEY_RETURN) && this.autoenter) e.stop(); this.autoenter = false; }.bind(this)); return li; }, createBox: function($super,text, options) { var li = $super(text, options); li.observe('mouseover',function() { this.addClassName('bit-hover'); }).observe('mouseout',function() { this.removeClassName('bit-hover') }); var a = new Element('a', { 'href': '#', 'class': 'closebutton' }); a.observe('click',function(e) { e.stop(); if(! this.current) this.focus(this.maininput); this.dispose(li); }.bind(this)); li.insert(a).cacheData('text', Object.toJSON(text)); return li; } }); Element.addMethods({ onBoxDispose: function(item,obj) { // Set to not to "add back" values in the drop-down upon delete if they were new values item = item.retrieveData('text').evalJSON(true); if(!item.newValue) obj.autoFeed(item); }, onInputFocus: function(el,obj) { obj.autoShow(); }, onInputBlur: function(el,obj) { obj.lastinput = el; if(!obj.curOn) { obj.blurhide = obj.autoHide.bind(obj).delay(0.1); } }, filter: function(D,E) { var C=[];for(var B=0,A=this.length;B - Distributed under MIT - Keep this message! */