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.
-
-
Million thanks, very useful information and it works perfectly.
-
To correctly post / get the data using update
Note: some of the code listed here is not correct – it has a syntax error causing it to fail.
Here is what you need to edit -
Your form needs to have an ID and a NAME. I chose files_form
Your input also needs and id and a name. I used files.
If you are not to familiar with JavaScript, then you will need to add a submit button to the form like this:
Note that there is no need for an onClick
Now open the file originally titled test.js
Do a search for tlist2
You will notice a line something like var tlist2 = new FacebookList(‘files’, ‘facebook-auto’);
That first parameter needs to be the ID/name of your input div
After you set that accordingly, place the cursor after the send();
Hit enter a few times to give yourself some room
Paste in this code
$(‘files_form’).addEvent(’submit’, function () { tlist2.update(); } );
The $(…) needs to contain the ID/NAME of your form. Note that the line I just pasted ends in a semicolon. The previous examples left that out, resulting in a syntax error and their code not working correctly.
If you are planning on submitting large amounts of data, then perhaps you should use post instead of get, by changing form method=”post”
Your data will be stored using the ID/NAME of the input tag. In my case, I can access my data using
$data = $_POST['files']
The data is (by default) separated by ###. If you would like to change that, look for this in the textboxlist.js (or the compressed version): separator: ‘###’,
Hope that helps, and thanks for this awesome resource!
Hamy