Hi zichun, thanks for all your hard work in writing this great control!
I'm trying to write an app which will allow users to enter multiple words (seperated by spaces) into one text box, with the autocomplete working on each word. At the moment your app only works for the first word in the text box. Sort of like in Gmail, where you can add another email address after the first one has auto-completed. Any idea how this could be done?
I´ve got the first version of this widget and tried to do some "stuff" on it... One of this changes was the support for multiple autocomplete, based on a separator to split the tokens into the text box... I´m sending de code below... hope it helps someone...
BTW: GREAT code, was the most complete i´ve found in the net... Thx!!!
Daniel Wolff (aka Bijur)
"A Native American elder once described his own inner struggles in
this manner: Inside of me there are two dogs. One of the dogs is mean
and evil. The other dog is good. The mean dog fights the good dog all
the time. When asked which dog wins, he reflected for a moment and
replied, The one I feed the most." (George Bernard Shaw)
-------CUT HERE----------
/* ---- Variables ---- */
var _separador = ","; // _separador.
var temp_evt = null;
var actb_timeOut = -1; // Autocomplete Timeout in ms (-1: autocomplete never time out)
var actb_lim = 8; // Number of elements autocomplete can show (-1: no limit)
var actb_firstText = false; // should the auto complete be limited to the beginning of keyword?
var actb_mouse = true; // Enable Mouse Support
/* ---- Variables ---- */
/* --- Styles --- */
var actb_bgColor = '#FFFFFF';
var actb_textColor = '#333333';
var actb_hColor = '#F0F0F0';
var actb_fFamily = 'Verdana';
var actb_fSize = '11px';
var actb_hStyle = 'text-decoration:underline;font-weight="bold"';
/* --- Styles --- */
/* ---- Constants ---- */
var actb_keywords = new Array();
var actb_display = false;
var actb_pos = 0;
var actb_total = 0;
var actb_curr = null;
var actb_rangeu = 0;
var actb_ranged = 0;
var actb_bool = new Array();
var actb_pre = 0;
var actb_toid;
var actb_tomake = false;
var actb_getpre = "";
var actb_mouse_on_list = false;
/* ---- Constants ---- */
function actb_parse(n){
var t = getToken(actb_curr.value, _separador);
t = t.replace("'", "\'");
t = t.replace('"', '\"');
var tobuild = '';
var i;
if (actb_firstText){
var re = new RegExp("^" + t, "i");
}else{
var re = new RegExp(t, "i");
}
var p = n.search(re);
for (i=0;i<p;i++){
tobuild += n.substr(i,1);
}
tobuild += "<font style='"+(actb_hStyle)+"'>"
for (i=p;i<t.length+p;i++){
tobuild += n.substr(i,1);
}
tobuild += "</font>";
for (i=t.length+p;i<n.length;i++){
tobuild += n.substr(i,1);
}
return tobuild;
}
function curTop(){
actb_toreturn = 0;
obj = actb_curr;
while(obj){
actb_toreturn += obj.offsetTop;
obj = obj.offsetParent;
}
return actb_toreturn;
}
function curLeft(){
actb_toreturn = 0;
obj = actb_curr;
while(obj){
actb_toreturn += obj.offsetLeft;
obj = obj.offsetParent;
}
return actb_toreturn;
}
function actb_generate(actb_bool){
if (document.getElementById('tat_table')) document.body.removeChild(document.getElementById('tat_table'));
a = document.createElement('table');
a.cellSpacing='3px';
a.cellPadding='2px';
a.style.borderStyle = 'solid';
a.style.borderWidth = '1px';
a.style.borderColor = '#333333';
a.style.cursor = 'pointer';
a.style.position='absolute';
a.style.top = eval(curTop() + actb_curr.offsetHeight) + "px";
a.style.left = curLeft() + "px";
a.style.backgroundColor=actb_bgColor;
a.id = 'tat_table';
if (actb_mouse){
a.onmouseover = actb_table_focus;
a.onmouseout= actb_table_unfocus;
}
document.body.appendChild(a);
var i;
var first = true;
var j = 1;
var counter = 0;
for (i=0;i<actb_keywords.length;i++){
if (actb_bool[i]){
counter++;
r = a.insertRow(-1);
if (first && !actb_tomake){
r.style.backgroundColor = actb_hColor;
first = false;
actb_pos = counter;
}else if(actb_pre == i){
r.style.backgroundColor = actb_hColor;
first = false;
actb_pos = counter;
}else{
r.style.backgroundColor = actb_bgColor;
}
r.id = 'tat_tr'+(j);
c = r.insertCell(-1);
c.style.color = actb_textColor;
c.style.fontFamily = actb_fFamily;
c.style.fontSize = actb_fSize;
c.innerHTML = actb_parse(actb_keywords[i]);
c.mouseover = "alert()";
c.id = 'tat_td'+(j);
if (actb_mouse) c.onclick=actb_mouseclick;
j++;
}
if (j - 1 == actb_lim && j < actb_total){
r = a.insertRow(-1);
r.style.backgroundColor = actb_bgColor;
c = r.insertCell(-1);
c.style.color = actb_textColor;
c.style.fontFamily = 'arial narrow';
c.style.fontSize = actb_fSize;
c.align='center';
c.innerHTML = '\\/';
if (actb_mouse){
c.onclick = actb_mouse_down;
}
break;
}
}
actb_rangeu = 1;
actb_ranged = j-1;
actb_display = true;
if (actb_pos <= 0) actb_pos = 1;
}
function actb_remake(){
document.body.removeChild(document.getElementById('tat_table'));
a = document.createElement('table');
a.cellSpacing='3px';
a.cellPadding='2px';
a.style.borderStyle = 'solid';
a.style.borderWidth = '1px';
a.style.borderColor = '#333333';
a.style.cursor = 'pointer';
a.style.position='absolute';
a.style.top = eval(curTop() + actb_curr.offsetHeight) + "px";
a.style.left = curLeft() + "px";
a.style.backgroundColor=actb_bgColor;
a.id = 'tat_table';
if (actb_mouse){
a.onmouseover = actb_table_focus;
a.onmouseout= actb_table_unfocus;
}
document.body.appendChild(a);
var i;
var first = true;
var j = 1;
if (actb_rangeu > 1){
r = a.insertRow(-1);
r.style.backgroundColor = actb_bgColor;
c = r.insertCell(-1);
c.style.color = actb_textColor;
c.style.fontFamily = 'arial narrow';
c.style.fontSize = actb_fSize;
c.align='center';
c.innerHTML = '/\\';
if (actb_mouse){
c.onclick = actb_mouse_up;
}
}
for (i=0;i<actb_keywords.length;i++){
if (actb_bool[i]){
if (j >= actb_rangeu && j <= actb_ranged){
r = a.insertRow(-1);
r.style.backgroundColor = actb_bgColor;
r.id = 'tat_tr'+(j);
c = r.insertCell(-1);
c.style.color = actb_textColor;
c.style.fontFamily = actb_fFamily;
c.style.fontSize = actb_fSize;
c.innerHTML = actb_parse(actb_keywords[i]);
c.id = 'tat_td'+(j);
if (actb_mouse) c.onclick=actb_mouseclick;
j++;
}else{
j++;
}
}
if (j > actb_ranged) break;
}
if (j-1 < actb_total){
r = a.insertRow(-1);
r.style.backgroundColor = actb_bgColor;
c = r.insertCell(-1);
c.style.color = actb_textColor;
c.style.fontFamily = 'arial narrow';
c.style.fontSize = actb_fSize;
c.align='center';
c.innerHTML = '\\/';
if (actb_mouse){
c.onclick = actb_mouse_down;
}
}
}
function actb_goup(){
if (!actb_display) return;
if (actb_pos == 1) return;
if (document.getElementById('tat_tr'+actb_pos))
document.getElementById('tat_tr'+actb_pos).style.backgroundColor = actb_bgColor;
actb_pos--;
if (actb_pos < actb_rangeu) actb_moveup();
if (document.getElementById('tat_tr'+actb_pos))
document.getElementById('tat_tr'+actb_pos).style.backgroundColor = actb_hColor;
if (actb_toid) clearTimeout(actb_toid);
if (actb_timeOut > 0) actb_toid = setTimeout("actb_removedisp()",actb_timeOut);
}
function actb_godown(){
if (!actb_display) return;
if (actb_pos == actb_total) return;
if (document.getElementById('tat_tr'+actb_pos))
document.getElementById('tat_tr'+actb_pos).style.backgroundColor = actb_bgColor;
actb_pos++;
if (actb_pos > actb_ranged) actb_movedown();
if (document.getElementById('tat_tr'+actb_pos))
document.getElementById('tat_tr'+actb_pos).style.backgroundColor = actb_hColor;
if (actb_toid) clearTimeout(actb_toid);
if (actb_timeOut > 0) actb_toid = setTimeout("actb_removedisp()",actb_timeOut);
}
function actb_movedown(){
actb_rangeu++;
actb_ranged++;
actb_remake();
}
function actb_moveup(){
actb_rangeu--;
actb_ranged--;
actb_remake();
}
function actb_mclick(n){
if (!actb_display) return;
actb_display = 0;
var word = '';
var c = 0;
for (var i=0;i<=actb_keywords.length;i++){
if (actb_bool[i]) c++;
if (c == n){
word = actb_keywords[i];
break;
}
}
a = word;//actb_keywords[actb_pos-1];//document.getElementById('tat_td'+actb_pos).;
actb_curr.value = addToken(actb_curr.value, a);
actb_removedisp();
}
/* Mouse */
function actb_mouse_down(){
document.getElementById('tat_tr'+actb_pos).style.backgroundColor = actb_bgColor;
actb_pos++;
actb_movedown();
document.getElementById('tat_tr'+actb_pos).style.backgroundColor = actb_hColor;
actb_curr.focus();
actb_moue_on_list = 0;
if (actb_toid) clearTimeout(actb_toid);
if (actb_timeOut > 0) actb_toid = setTimeout("actb_removedisp()",actb_timeOut);
}
function actb_mouse_up(){
document.getElementById('tat_tr'+actb_pos).style.backgroundColor = actb_bgColor;
actb_pos--;
actb_moveup();
document.getElementById('tat_tr'+actb_pos).style.backgroundColor = actb_hColor;
actb_curr.focus();
actb_moue_on_list = 0;
if (actb_toid) clearTimeout(actb_toid);
if (actb_timeOut > 0) actb_toid = setTimeout("actb_removedisp()",actb_timeOut);
}
function actb_mouseclick(){
if (!actb_display) return;
actb_mouse_on_list = 0;
actb_display = 0;
actb_curr.value = addToken(actb_curr.value, this.innerHTML.replace(/<[^>]+>/g,""));
actb_removedisp();
actb_curr.focus();
}
function actb_table_focus(){
actb_mouse_on_list = 1;
}
function actb_table_unfocus(){
actb_mouse_on_list = 0;
if (actb_toid) clearTimeout(actb_toid);
if (actb_timeOut > 0) actb_toid = setTimeout("actb_removedisp()",actb_timeOut);
}
/* ---- */
function actb_penter()
{
if (!actb_display)
return;
actb_display = 0;
var word = '';
var c = 0;
for (var i=0;i<=actb_keywords.length;i++){
if (actb_bool[i]) c++;
if (c == actb_pos){
word = actb_keywords[i];
break;
}
}
a = word;//actb_keywords[actb_pos-1];//document.getElementById('tat_td'+actb_pos).;
//actb_curr.value = a;
actb_pos = 0;
actb_curr.value = addToken(actb_curr.value, a);
actb_removedisp();
}
function actb_removedisp(){
if (!actb_mouse_on_list) {
actb_display = 0;
if (document.getElementById('tat_table')) document.body.removeChild(document.getElementById('tat_table'));
if (actb_toid) clearTimeout(actb_toid);
}
}
function actb_checkkey(evt){
a = evt.keyCode;
if (a == 38){ // up key
actb_goup();
}else if(a == 40){ // down key
actb_godown();
}else if(a == 13){
actb_penter();
}
}
function actb_tocomplete(sndr,evt,arr, separador){
if (separador)
_separador = separador;
else
_separador = ";";
if (arr) actb_keywords = arr;
if (!sndr) var sndr = evt.srcElement;
actb_curr = sndr;
if (evt.keyCode == 38 || evt.keyCode == 40 || evt.keyCode == 13) return;
var i;
var word = 0;
var c = 0;
for (var i=0;i<=actb_keywords.length;i++){
if (actb_bool[i]) c++;
if (c == actb_pos){
word = i;
break;
}
}
actb_pre = word;//actb_pos;
var t = sndr.value.replace("'","\'");
t = t.replace('"','\"');
t = getToken(t, _separador);
if (actb_firstText){
var re = new RegExp("^" + t, "i");
}else{
var re = new RegExp(t, "i");
}
actb_total = 0;
actb_tomake = false;
for (i=0;i<actb_keywords.length;i++){
actb_bool[i] = false;
if (re.test(actb_keywords[i])){
actb_total++;
actb_bool[i] = true;
if (actb_pre == i) actb_tomake = true;
}
}
if (actb_toid) clearTimeout(actb_toid);
if (actb_timeOut > 0) actb_toid = setTimeout("actb_removedisp()",actb_timeOut);
actb_generate(actb_bool);
}
function getToken(valor, _separador)
{
sep = _separador + ' ';
if (valor.lastIndexOf(sep) > 0)
return valor.substr((valor.lastIndexOf(sep)+2));
else
return valor;
}
I like your code. But I found one problem.
If the array contains groups of words, for example:
"Manchester United", "Chelsea FC", "FC Porto"
with spaces, when you type the text in the text box and get to the space character, the find does not work any more.
I have managed to write a solution for the code to find the space character and other special ones.
Here are the changes:
1) At the function actb_tocomplete, go to the line if (re.test((actb_keywords[i])){ and change it to if (re.test(escape(actb_keywords[i]))){
2) At the functions actb_remake() and actb_generate() find the line c.innerHTML = actb_parse(actb_keywords[i]); and change it to c.innerHTML = actb_parse(escape(actb_keywords[i]));
3) Finally, at the function actb_parse(n), change the line return tobuild; to return unescape(tobuild);
Basically, all that I did was to escape (encode) all the text for the code to find everything and, then, unescape (unencode) it just before displaying it.
I think that's all. Please, let me know if it didn't work because I could have forgotten something...
(just updated: I already found a bug with this... Working on it...)
Two ways to prevent SELECT to come over the layer:
* Design you page so its not a problem
* Detect layers position and boundries and set visibility to hidden for any SELECT elements within those boundries.
------------------------------------------
Hey! Stop reading my signature... stop it!
hmm... you're still reading it...
Hey this work in a great way,this solves the problem where the list of values displayed by an autocomplete filed will not be overlapped by a combo box..i.e the list of values doesn't hide behind combo box..geat idea with less complexity and serves the purpose to maximum extent...great stuff...
Sorry to burst your bubble... In IE 6 the program works, kinda... the div is not at the correct location (it's aligned to the left side of the window about 100 px above the control.... I didn't notice it immidately, nor was it obvious what it was for.
In Firebird/Mozilla or netscape prob (don't have netscape installed to test) the div is located at the end of the page (2 screens of content away and off the screen from the control!) and there's a javascript error (Highlight is not defined)
Maybe you should fix the positioning of the div.
I've done a similar control at work, but they're paranoid about stuff so can't show it to you... , but keep up the good work.
I have tested this with Opera 7.50, Mozilla 1.7.1 and IE 6 and it worked everywhere. The only problem with Opera was that the tooltip window filled almost the full width of the page.
I have also tried it on the linux, just for fun, using Mozilla 1.7 and Konqueror 3.2.0. Mozilla worked fine, but with Konqueror the functionality was rather limited. That is, the tooltip window appeared, and upon pressing Enter the highlighted entry was pasted into the textbox, but the up/down arrow keys didn't so I couldn't choose from the suggested entries.
Anyway, this looks like a very nice and useful control!