TextboxList meets Autocompletion
This post discusses a project which has its own page. Please refer to TextboxList for the most up-to-date information.

In my previous blogpost I explained how to extend TextboxList to add closing functionality via a link added to each box. But it was missing an important ingredient: autocompletion!
Again, all we have to do is extend the TextboxList class, override some methods, some events, and create some new ones (all prefixed by auto)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | var FacebookList = new Class({ Extends: TextboxList, data: [], options: { onInputFocus: function() { this.autoShow(); }, onInputBlur: function(el) { el.value = ''; this.autoHide(); }, onBoxDispose: function(item) { this.autoFeed(item.$attributes.$text); }, autocomplete: { 'opacity': 0.8, 'maxresults': 10, 'minchars': 1 } }, initialize: function(element, autoholder, options) { arguments.callee.parent(element, options); this.autoholder = $(autoholder).set('opacity', this.options.autocomplete.opacity); this.autoresults = this.autoholder.getElement('ul'); var children = this.autoresults.getElements('li'); children.each(function(el) { this.add(el.innerHTML); }, this); }, autoShow: function(search) { this.autoholder.setStyle('display', 'block'); this.autoholder.getElements('*').setStyle('display', 'none'); if(! search || ! search.trim() || (! search.length || search.length < this.options.autocomplete.minchars )) { this.autoholder.getElement('.default').setStyle('display', 'block'); this.resultsshown = false; } else { this.resultsshown = true; this.autoresults.setStyle('display', 'block').empty(); this.data.filter(function(str) { return str ? str.test(search, 'i') : false; }).each(function(result, ti) { if(ti >= this.options.autocomplete.maxresults) return; var el = new Element('li').set('html', this.autoHighlight(result, search)).inject(this.autoresults); el.$attributes.$result = result; if(ti == 0) this.autoFocus(el); }, this); } }, autoHighlight: function(html, highlight) { return html.replace(new RegExp(highlight, 'gi'), function(match) { return '<em>' + match + '</em>'; }); }, autoHide: function() { this.resultsshown = false; this.autoholder.setStyle('display', 'none'); }, autoFocus: function(el) { if(! el) return; if(this.autocurrent) this.autocurrent.removeClass('auto-focus'); this.autocurrent = el.addClass('auto-focus'); }, autoMove: function(direction) { if(!this.resultsshown) return; this.autoFocus(this.autocurrent['get' + (direction == 'up' ? 'Previous' : 'Next')]()); }, autoFeed: function(text) { if(this.data.indexOf(text) == -1) this.data.push(text); }, autoAdd: function(el) { if(!el || ! el.$attributes.$result) return; this.add(el.$attributes.$result); delete this.data[this.data.indexOf(el.$attributes.$result)]; this.autoHide(); this.current.$attributes.$input.value = ''; }, createInput: function(options) { var li = arguments.callee.parent(options); var input = li.$attributes.$input; input.addEvents({ 'keydown': function(e) { e = new Event(e); this.dosearch = false; switch(e.code) { case Event.Keys.up: return this.autoMove('up'); case Event.Keys.down: return this.autoMove('down'); case Event.Keys.enter: this.autoAdd(this.autocurrent); this.autocurrent = false; this.autoenter = true; break; default: this.dosearch = true; } }.bind(this), 'keyup': function() { if(this.dosearch) this.autoShow(input.value); }.bind(this) }); input.addEvent(Browser.Engine.trident ? 'keydown' : 'keypress', function(e) { if(this.autoenter) new Event(e).stop(); this.autoenter = false; }.bind(this)); return li; }, createBox: function(text, options) { var li = arguments.callee.parent(text, options); li.addEvents({ 'mouseenter': function() { this.addClass('bit-hover') }, 'mouseleave': function() { this.removeClass('bit-hover') } }); li.adopt(new Element('a', { 'href': '#', 'class': 'closebutton', 'events': { 'click': function(e) { new Event(e).stop(); if(! this.current) this.focus(this.maininput); this.dispose(li); }.bind(this) } })); li.$attributes.$text = text; return li; } }); window.addEvent('domready', function() { // init var tlist2 = new FacebookList('facebook-demo', 'facebook-auto'); // fetch and feed new Request.JSON({'url': 'json.html', 'onComplete': function(j) { j.each(tlist2.autoFeed, tlist2); }}).send(); }); |
It works by caching all the results from a JSON Request and feeding them to the autocompleter object. When a item is added as a box, it’ removed from the feed array, and when the box is disposed it’s added back, so that it becomes available in the list when the user types.
Another new feature is that you’ll be able to let it add boxes from the HTML directly:
1 2 3 4 5 6 7 8 9 | <label>FacebookList input</label> <input type="text" value="" id="facebook-demo" /> <div id="facebook-auto"> <div class="default">Type the name of an argentine writer you like</div> <ul class="feed"> <li>Jorge Luis Borges</li> <li>Julio Cortazar</li> </ul> </div> |
The constructor now takes new parameters to configure the autocompletion, like the minimum number of characters to trigger the dropdown, and more.
Changelog
- 0.1: initial release
- 0.2: added click support, removed $attributes use, code cleanup
Download
Click here to download the zip with code and examples
Displaying only a subset of the comments. Click here to display all comments.
-
-
Nevermind. Found the solution in your more recent project.
http://devthought.com/projects/mootools/textboxlist/
Autosuggest has a url and param request. With those I can do everything I need.
-
So I have a database with about 8,000 entries (minimum, this is subject to grow). Naturally I don’t want to have a PHP file read the entire contents of the database and send back all 8,000 items to be cached and used in the auto-suggest. That’s just ridiculous. Rather, I’d prefer to select only those entries which begin with whatever the user has already typed (so if you type “aut”, it will turn up things like “automobile”, “automatic”, “autism”, etc)
I could do this if instead of sending the data to json.html it were sent to json.php?tag=aut, however I don’t know how to get the content of the input box while it’s still being edited. Help would be appreciated.