1 // $Id: autocomplete.js 144 2007-03-28 07:52:20Z thierry $
5 addLoadEvent(autocompleteAutoAttach);
9 * Attaches the autocomplete behaviour to all required fields
11 function autocompleteAutoAttach() {
13 var inputs = document.getElementsByTagName('input');
14 for (i = 0; input = inputs[i]; i++) {
15 if (input && hasClass(input, 'autocomplete')) {
18 acdb[uri] = new ACDB(uri);
20 input = $(input.id.substr(0, input.id.length - 13));
21 input.setAttribute('autocomplete', 'OFF');
22 addSubmitEvent(input.form, autocompleteSubmit);
23 new jsAC(input, acdb[uri]);
29 * Prevents the form from submitting if the suggestions popup is open
31 function autocompleteSubmit() {
32 var popup = document.getElementById('autocomplete');
34 popup.owner.hidePopup();
42 * An AutoComplete object
44 function jsAC(input, db) {
48 this.input.onkeydown = function (event) { return ac.onkeydown(this, event); };
49 this.input.onkeyup = function (event) { ac.onkeyup(this, event) };
50 this.input.onblur = function () { ac.hidePopup(); ac.db.cancel(); };
51 this.popup = document.createElement('div');
52 this.popup.id = 'autocomplete';
53 this.popup.owner = this;
57 * Hides the autocomplete suggestions
59 jsAC.prototype.hidePopup = function (keycode) {
60 if (this.selected && ((keycode && keycode != 46 && keycode != 8 && keycode != 27) || !keycode)) {
61 this.input.value = this.selected.autocompleteValue;
63 if (this.popup.parentNode && this.popup.parentNode.tagName) {
64 removeNode(this.popup);
66 this.selected = false;
71 * Handler for the "keydown" event
73 jsAC.prototype.onkeydown = function (input, e) {
78 case 40: // down arrow
84 default: // all other keys
90 * Handler for the "keyup" event
92 jsAC.prototype.onkeyup = function (input, e) {
100 case 20: // caps lock
102 case 34: // page down
105 case 37: // left arrow
107 case 39: // right arrow
108 case 40: // down arrow
114 this.hidePopup(e.keyCode);
117 default: // all other keys
118 if (input.value.length > 0)
119 this.populatePopup();
121 this.hidePopup(e.keyCode);
127 * Puts the currently highlighted suggestion into the autocomplete field
129 jsAC.prototype.select = function (node) {
130 this.input.value = node.autocompleteValue;
134 * Highlights the next suggestion
136 jsAC.prototype.selectDown = function () {
137 if (this.selected && this.selected.nextSibling) {
138 this.highlight(this.selected.nextSibling);
141 var lis = this.popup.getElementsByTagName('li');
142 if (lis.length > 0) {
143 this.highlight(lis[0]);
149 * Highlights the previous suggestion
151 jsAC.prototype.selectUp = function () {
152 if (this.selected && this.selected.previousSibling) {
153 this.highlight(this.selected.previousSibling);
158 * Highlights a suggestion
160 jsAC.prototype.highlight = function (node) {
161 removeClass(this.selected, 'selected');
162 addClass(node, 'selected');
163 this.selected = node;
167 * Unhighlights a suggestion
169 jsAC.prototype.unhighlight = function (node) {
170 removeClass(node, 'selected');
171 this.selected = false;
175 * Positions the suggestions popup and starts a search
177 jsAC.prototype.populatePopup = function () {
179 var pos = absolutePosition(this.input);
180 this.selected = false;
181 this.popup.style.top = (pos.y + this.input.offsetHeight) +'px';
182 this.popup.style.left = pos.x +'px';
183 this.popup.style.width = (this.input.offsetWidth - 4) +'px';
184 this.db.owner = this;
185 this.db.search(this.input.value);
189 * Fills the suggestion popup with any matches received
191 jsAC.prototype.found = function (matches) {
192 while (this.popup.hasChildNodes()) {
193 this.popup.removeChild(this.popup.childNodes[0]);
195 if (!this.popup.parentNode || !this.popup.parentNode.tagName) {
196 document.getElementsByTagName('body')[0].appendChild(this.popup);
198 var ul = document.createElement('ul');
201 for (key in matches) {
202 var li = document.createElement('li');
203 var div = document.createElement('div');
204 div.innerHTML = matches[key];
206 li.autocompleteValue = key;
207 li.onmousedown = function() { ac.select(this); };
208 li.onmouseover = function() { ac.highlight(this); };
209 li.onmouseout = function() { ac.unhighlight(this); };
213 if (ul.childNodes.length > 0) {
214 this.popup.appendChild(ul);
219 removeClass(this.input, 'throbbing');
223 * An AutoComplete DataBase object
232 * Performs a cached and delayed search
234 ACDB.prototype.search = function(searchString) {
235 this.searchString = searchString;
236 if (this.cache[searchString]) {
237 return this.owner.found(this.cache[searchString]);
240 clearTimeout(this.timer);
243 this.timer = setTimeout(function() {
244 addClass(db.owner.input, 'throbbing');
245 db.transport = HTTPGet(db.uri +'/'+ encodeURIComponent(searchString), db.receive, db);
250 * HTTP callback function. Passes suggestions to the autocomplete object
252 ACDB.prototype.receive = function(string, xmlhttp, acdb) {
253 // Note: Safari returns 'undefined' status if the request returns no data.
254 if (xmlhttp.status != 200 && typeof xmlhttp.status != 'undefined') {
255 removeClass(acdb.owner.input, 'throbbing');
256 return alert('An HTTP error '+ xmlhttp.status +' occured.\n'+ acdb.uri);
259 var matches = parseJson(string);
260 if (typeof matches['status'] == 'undefined' || matches['status'] != 0) {
261 acdb.cache[acdb.searchString] = matches;
262 acdb.owner.found(matches);
267 * Cancels the current autocomplete request
269 ACDB.prototype.cancel = function() {
270 if (this.owner) removeClass(this.owner.input, 'throbbing');
271 if (this.timer) clearTimeout(this.timer);
272 if (this.transport) {
273 this.transport.onreadystatechange = function() {};
274 this.transport.abort();