Here's some JS code that runs an AJAX autocomplete menu. It uses mootools for its AJAX calls (v1.2 required). This is what we use on beeets.com for searching tags.
This code sends an AJAX request to whatever URL is specified during initialization, and puts the response (should normally be a <ul><li> list) into a div right under the input box. Example CSS and HTML is included.
var autoComplete = new Class({
options: {
field: false,
cn: false,
cur_li: -1,
lis: -1,
url: '',
type: '',
is_open: false,
req_num: 0,
delay: 100
},
initialize: function(url, field, type, delay) {
if(!delay) delay = 200;
this.url = url;
this.field = field;
this.type = type;
this.delay = delay;
var _ac = this;
this.req_num = 0;
_ac.timer = new Timer(delay);
_ac.oldurl = null;
this.field.setAttribute('autocomplete', 'off');
this.field.addEvent('keyup', function(e) {
e = new Event(e);
if(e.key == 'backspace' || e.key == 'delete' || e.key == 'left' || e.key == 'right' || e.key == 'up' || e.key == 'down' || e.key == 'enter') {
_ac.keyProcess(e.key);
} else if(e.key != 'esc') {
_ac.run();
}
});
this.field.addEvent('keydown', function(e) {
e = new Event(e);
if(e.key == 'enter') {
if(_ac.is_open) {
e.stop();
_ac.keyProcess(e.key);
}
} else if (e.key == 'esc' || (e.control && e.key == 'w') || (e.control && e.key == 'x')) {
_ac.clear();
}
});
document.addEvent('click', function(e) {
var e = new Event(e);
var mx = e.page.x;
var my = e.page.y;
if(_ac.cn) {
var cnc = _ac.cn.getCoordinates();
if(mx < cnc.left || mx > cnc.right || my < cnc.top || my > cnc.bottom) {
_ac.clear();
}
}
});
},
run: function(url) {
var _ac = this;
var field = this.field;
var srch = field.getProperty('value').replace(/ /, '%20');
url = (!url) ? _ac.url : url;
if(srch == _ac.oldurl) {
return false;
}
_ac.oldurl = srch;
if(this.timer.is_started) {
_ac.timer.reset();
} else {
_ac.timer.start();
}
var dorun = function() {
_ac.timer.stop();
if(field.getProperty('value').replace(/^[ \t]$/, '') == '') return;
_ac.req_num++;
var _count = _ac.req_num;
new Request({
url: url,
data: 'type='+_ac.type+'&str='+srch,
evalScripts: true,
method: 'get',
onComplete: function (res) {
if(_count != _ac.req_num) {
return false;
}
var cn = $('tag_autocom');
if(cn) cn.dispose();
if(res.match(/^\t*$/)) {
_ac.clear();
return;
}
var cn = $(document.createElement('div'));
cn.id = 'tag_autocom';
cn.className = 'autocomplete clear';
cn.set('html', res);
cn.style.marginTop = (field.offsetHeight) + 'px';
cn.injectAfter(field);
_ac.cur_li = -1;
_ac.cn = cn;
_ac.is_open = true;
_ac.initLIs();
}
}).send();
}.bind(this);
_ac.timer.end = dorun;
},
initLIs: function() {
var _ac = this;
var cn = this.cn;
var field = this.field;
var lis = $ES('li', cn);
this.lis = lis;
lis.each(function(el, i) {
el.addEvent('click', function () {
var itm = el.clone();
itm.id = el.id;
var tmp = el.innerHTML.replace(/\<.*?\>/, '').replace(/\&/ig, '&');;
_ac.clear();
field.value = tmp;
field.focus();
_ac.finish(itm);
});
el.addEvent('mouseover', function() {
lis.each(function(el) {el.className = el.className.replace(/sel/g, ''); });
this.cur_li = i;
el.className += ' sel';
}.bind(this));
el.addEvent('mouseout', function() {
el.className = el.className.replace(/sel/g, '');
});
}.bind(this));
if(this.cur_li >= 0) lis[this.cur_li].className += ' sel';
},
keyProcess: function(key) {
if(!this.cn && (key == 'down' || key == 'backspace' || key == 'delete') && this.field.value != '') {
this.run();
} else if(!this.cn) {
return;
}
if(key == 'up' || key == 'down' || key == 'enter' || key == 'backspace' || key == 'delete') {
if(key == 'up' && this.cur_li > 0) {
this.cur_li--;
this.clearLIs();
this.lis[this.cur_li].className += 'sel';
this.field.value = this.lis[this.cur_li].innerHTML.replace(/\&/ig, '&');;
} else if(key == 'down' && this.cur_li < (this.lis.length - 1)) {
this.cur_li++;
this.clearLIs();
this.lis[this.cur_li].className += 'sel';
this.field.value = this.lis[this.cur_li].innerHTML.replace(/\&/ig, '&');;
} else if(key == 'enter' && this.cn) {
if(this.cur_li >= 0) {
var itm = this.lis[this.cur_li].clone();
itm.id = this.lis[this.cur_li].id;
var tmp = this.lis[this.cur_li].innerHTML.replace(/\<.*?\>/, '').replace(/\&/ig, '&');;
this.clear();
this.field.value = tmp;
}
this.clear();
this.finish(itm);
} else if(key == 'backspace' || key == 'delete') {
if(this.field.value == '') {
this.clear();
} else {
this.run();
}
}
}
},
clear: function() {
if(this.cn && this.cn.getParent()) {
this.cn.dispose();
}
this.is_open = false;
this.cn = false;
this.cur_li = -1;
this.lis = false;
},
clearLIs: function() {
this.lis.each(function(el){
el.className = el.className.replace(/sel/g, '');
});
},
finish: function() {
// override me!
var _tmp = $(this.field.getParent());
for(i = 0; i < 5; i++) {
if(_tmp.nodeName.toLowerCase() == 'form') {
_tmp.type = 'submit';
_tmp.fireEvent('submit', _tmp);
break;
} else {
_tmp = _tmp.getParent();
}
}
}
});
autoComplete.implement(new Events, new Options);