/* Auto-suggest control, modified for Allegro's purposes.
*
* The original copyright notice follows:
*
* Auto-suggest control, version 2.4, October 10th 2009.
*
* (c) 2007-2009 Dmitriy Khudorozhkov (dmitrykhudorozhkov@yahoo.com)
*
* Latest version download and documentation:
* http://www.codeproject.com/KB/scripting/AutoSuggestControl.aspx
*
* Based on "Auto-complete Control" by zichun:
* http://www.codeproject.com/KB/scripting/jsactb.aspx
*
* This software is provided "as-is", without any express or implied warranty.
* In no event will the author be held liable for any damages arising from the
* use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
*
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
*
* 3. This notice may not be removed or altered from any source distribution.
*/
var autosuggest_url = ""; // Global link to the server-side script, that gives you the suggestion list.
// Used for controls that do not define their own server script urls.
function autosuggest(id, array, url, onSelect)
{
var field = document.getElementById(id);
var exists = field.autosuggest;
if(exists) return exists;
// "Public" variables:
this.time_out = 0; // autocomplete timeout, in milliseconds (0: autocomplete never times out)
this.response_time = 250; // time, in milliseconds, between the last char typed and the actual query
this.entry_limit = 10; // number of entries autocomplete will show at a time
this.limit_start = false; // should the auto complete be limited to the beginning of keyword?
this.match_first = true; // if previous is false, should the exact matches be displayed first?
this.restrict_typing = false; // restrict to existing members of array
this.full_refresh = false; // should the script re-send the AJAX request after each typed character?
this.use_iframe = true; // should the control use an IFrame element to fix suggestion list positioning (MS IE only)?
this.use_scroll = true; // should the control use a scroll bar (true) or a up/down arrow-buttons (false)?
this.use_mouse = true; // enable mouse support
this.no_default = false; // should the control omit selecting the 1st item in a suggestion list?
this.start_check = 0; // show widget only after this number of characters is typed in (effective if >1)
this.text_delimiter = [";", ","]; // delimiter for multiple autocomplete entries. Set it to empty array ( [] ) for single autocomplete.
this.ajax_delimiter = "|"; // character that delimits entries in the string returned by AJAX call
this.item_delimiter = ","; // character that delimits key and value for the suggestion item in the string returned by AJAX call
this.selectedIndex = -1; // index (zero-based) of the entry last selected
this.min_results = 5; // minimum number of results to return
// "Private" variables:
this.suggest_url = url || (array ? "" : autosuggest_url); // URL the server-side script that gives you the suggestion list
this.msie = (document.all && !window.opera);
this.displayed = false;
this.delim_words = [];
this.current_word = 0;
this.delim_char = [];
this.current = 0;
this.total = 0;
this.range_up = 0;
this.range_down = 0;
this.previous = 0;
this.timer = 0;
this.rebuild = false;
this.evsetup = false;
this.bool = [];
this.rows = [];
this.onSelect = onSelect || null;
this.cur_x = 0;
this.cur_y = 0;
this.cur_w = 0;
this.cur_h = 0;
this.mouse_x = 0;
this.mouse_y = 0;
this.mouse_on_list = 0;
this.caret_moved = false;
this.field_id = id;
this.field = field;
this.lastterm = field.value;
this.keywords = [], this.keywords_init = [];
this.values = [], this.values_init = [];
return this.construct(array || []);
};
autosuggest.prototype = {
construct: function(array)
{
function callLater(func, obj, param1, param2) { return function() { func.call(obj, param1 || null, param2 || null) }; }
this.field.autosuggest = this;
// Initialize the control from JS array, if any:
this.bindArray(array);
// Create event handlers:
this.funcClick = this.mouseClick;
this.funcCheck = this.checkKey;
this.funcPress = this.keyPress;
this.funcHighlight = this.highlightTable;
this.funcClear = callLater(this.clearEvents, this);
this.funcUp = callLater(this.scroll, this, true, 1);
this.funcDown = callLater(this.scroll, this, false, 1);
this.funcFocus = callLater(this.focusTable, this);
this.funcUnfocus = callLater(this.unfocusTable, this);
this.addEvent(this.field, "focus", callLater(this.setupEvents, this));
this.addEvent(window, "resize", callLater(this.reposition, this));
var obj = this;
this.addEvent(document, "keypress", function(event) { obj.funcShortcut(obj, event); });
this.addEvent(document, "keydown", function(event) { obj.funcShortcut(obj, event); });
return this;
},
funcShortcut: function(obj, event)
{
event = event || window.event;
var s;
if("key" in event && typeof event.key != "undefined")
{
s = event.key;
}
else
{
var c = event.charCode || event.keyCode;
if(c == 27)
{
s = "Escape";
}
else
{
s = String.fromCharCode(c);
}
}
switch(s)
{
case "Escape":
obj.field.blur();
break;
case "s":
case "S":
if(!this.evsetup)
{
event.preventDefault();
obj.field.focus();
break;
}
}
},
bindArray: function(array)
{
if(!array || !array.length) return;
this.suggest_url = "";
this.keywords = [], this.keywords_init = [];
this.values = [], this.values_init = [];
for(var i = 0, cl = array.length; i < cl; i++)
{
var item = array[i];
if(item.constructor == Array)
{
this.keywords[i] = this.keywords_init[i] = item[0];
this.values[i] = this.values_init[i] = item[1];
}
else
{
this.keywords[i] = this.keywords_init[i] = item;
this.values[i] = this.values_init[i] = "";
}
}
},
bindURL: function(url)
{
if(!url)
url = autosuggest_url;
this.suggest_url = url;
},
setupEvents: function()
{
if(!this.evsetup)
{
this.evsetup = true;
this.addEvent(document, "keydown", this.funcCheck);
this.addEvent(this.field, "blur", this.funcClear);
this.addEvent(document, "keypress", this.funcPress);
}
},
clearEvents: function()
{
// Removes an event handler:
function removeEvent(obj, event_name, func_ref)
{
if(obj.removeEventListener && !window.opera)
{
obj.removeEventListener(event_name, func_ref, true);
}
else if(obj.detachEvent)
{
obj.detachEvent("on" + event_name, func_ref);
}
else
{
obj["on" + event_name] = null;
}
}
var event = window.event;
if(event && this.cur_h)
{
var elem = event.srcElement || event.target;
var x = this.mouse_x + (document.documentElement.scrollLeft || document.body.scrollLeft || 0);
var y = this.mouse_y + (document.documentElement.scrollTop || document.body.scrollTop || 0);
if((elem.id == this.field_id) && (x > this.cur_x && x < (this.cur_x + this.cur_w)) && (y > this.cur_y && y < (this.cur_y + this.cur_h)))
{
this.field.focus();
return;
}
}
removeEvent(document, "keydown", this.funcCheck);
removeEvent(this.field, "blur", this.funcClear);
removeEvent(document, "keypress", this.funcPress);
this.hide();
this.evsetup = false;
},
parse: function(n, plen, re)
{
if(!n || !n.length) return "";
if(!plen) return n;
var tobuild = [], c = 0, p = n.search(re);
// No match found because we're serving approximate results
if(p < 0)
{
tobuild[c++] = n;
}
else
{
tobuild[c++] = n.substr(0, p);
tobuild[c++] = "";
tobuild[c++] = n.substring(p, plen + p);
tobuild[c++] = "";
tobuild[c++] = n.substring(plen + p, n.length);
}
return tobuild.join("");
},
build: function()
{
if(this.total == 0)
{
this.displayed = false;
return;
}
this.rows = [];
this.current = this.no_default ? -1 : 0;
var that = this;
this.addEvent(document, "mousemove", function(event)
{
event = event || window.event;
that.mouse_x = event.x;
that.mouse_y = event.y;
});
var body = document.getElementById("suggest_table_" + this.field_id);
if(body)
{
this.displayed = false;
document.body.removeChild(body);
var helper = document.getElementById("suggest_helper_" + this.field_id);
if(helper)
document.body.removeChild(helper);
}
var bb = document.createElement("div");
bb.id = "suggest_table_" + this.field_id;
bb.className = "autosuggest-body";
this.cur_y = this.curPos(this.field, "Top") + this.field.offsetHeight;
bb.style.top = this.cur_y + "px";
this.cur_x = this.curPos(this.field, "Left");
bb.style.left = this.cur_x + "px";
this.cur_w = this.field.offsetWidth - (this.msie ? 2 : 6);
bb.style.width = this.cur_w + "px";
this.cur_h = 1;
bb.style.height = "1px";
var cc = null;
if(this.msie && this.use_iframe)
{
var cc = document.createElement("iframe");
cc.id = "suggest_helper_" + this.field_id;
cc.src = "javascript:\"\";";
cc.scrolling = "no";
cc.frameBorder = "no";
}
var that = this;
var showFull = (this.total > this.entry_limit);
if(cc)
{
document.body.appendChild(cc);
cc.style.top = this.cur_y + "px";
cc.style.left = this.cur_x + "px";
cc.style.width = bb.offsetWidth + 2;
}
document.body.appendChild(bb);
var first = true, dispCount = showFull ? this.entry_limit : this.total;
var str = [], cn = 0;
// cellspacing and cellpadding were not moved to css - IE doesn't understand border-spacing.
str[cn++] = "
";
bb.innerHTML = str.join("");
var table = bb.firstChild;
if(this.use_mouse)
{
table.onmouseout = this.funcUnfocus;
table.onmouseover = this.funcFocus;
}
var real_height = 0, real_width = 0;
function createArrowRow(dir)
{
var row = table.insertRow(-1);
row.className = dir ? "up" : "down";
var cell = row.insertCell(0);
real_height += cell.offsetHeight + 1;
return cell;
}
if(!this.use_scroll && showFull)
createArrowRow(true).parentNode.className = "up-disabled";
var kl = this.keywords.length, counter = 0, j = 0;
// For "parse" call:
var t, plen;
if(this.text_delimiter.length > 0)
{
var word = this.delim_words[this.current_word];
t = this.trim(this.addSlashes(word));
plen = this.trim(word).length;
}
else
{
var word = this.field.value;
t = this.addSlashes(word);
plen = word.length;
}
var re = new RegExp((this.limit_start ? "^" : "") + t, "i");
function addSuggestion(index, _first)
{
var row = that.rows[j] = table.insertRow(-1);
row.className = (_first || (that.previous == index)) ? "selected" : "";
var cell = row.insertCell(0);
cell.innerHTML = that.parse(that.keywords[index], plen, re);
cell.setAttribute("pos", j++);
cell.autosuggest = that;
if(that.use_mouse)
{
that.addEvent(cell, "click", that.funcClick);
cell.onmouseover = that.funcHighlight;
}
return [row.offsetWidth, row.offsetHeight];
}
for(var i = 0; i < kl; i++)
{
if(this.bool[i])
{
var dim = addSuggestion(i, (first && !this.no_default && !this.rebuild));
first = false;
if(counter <= this.entry_limit)
real_height += dim[1] + 1;
if(real_width < dim[0])
real_width = dim[0];
if(++counter == this.entry_limit)
{
++i;
break;
}
}
}
var last = i;
if(showFull)
{
if(!this.use_scroll)
{
var cell = createArrowRow(false);
if(this.use_mouse)
this.addEvent(cell, "click", this.funcDown);
}
else
{
bb.style.height = real_height + "px";
bb.style.overflow = "auto";
bb.style.overflowX = "hidden";
}
}
this.cur_h = real_height + 1;
bb.style.height = this.cur_h + "px";
this.cur_w = ((real_width > bb.offsetWidth) ? real_width : bb.offsetWidth) + (this.msie ? -2 : 2) + 100;
bb.style.width = this.cur_w + "px";
if(cc)
{
cc.style.height = this.cur_h + "px";
cc.style.width = this.cur_w + "px";
}
this.range_up = 0;
this.range_down = j - 1;
this.displayed = true;
if(this.use_scroll)
{
setTimeout(function()
{
counter = 0;
for(var i = last; i < kl; i++)
{
if(!that.displayed) return;
if(that.bool[i])
{
addSuggestion(i);
if(++counter == that.entry_limit)
{
++i;
break;
}
}
}
last = i;
if(j < that.total) setTimeout(arguments.callee, 25);
},
25);
}
},
remake: function()
{
this.rows = [];
var a = document.getElementById("suggest_table2_" + this.field_id);
var k = 0, first = true;
function adjustArrow(obj, which, cond, handler)
{
var r = a.rows[k++];
r.className = which ? (cond ? "up" : "up-disabled") : (cond ? "down" : "down-disabled");
var c = r.firstChild;
if(cond && handler && obj.use_mouse)
obj.addEvent(c, "click", handler);
}
if(this.total > this.entry_limit)
{
var b = (this.range_up > 0);
adjustArrow(this, true, b, this.funcUp);
}
// For "parse" call:
var t, plen;
if(this.text_delimiter.length > 0)
{
var word = this.delim_words[this.current_word];
t = this.trim(this.addSlashes(word));
plen = this.trim(word).length;
}
else
{
var word = this.field.value;
t = this.addSlashes(word);
plen = word.length;
}
var re = new RegExp((this.limit_start ? "^" : "") + t, "i");
var kl = this.keywords.length, j = 0;
for(var i = 0; i < kl; i++)
{
if(this.bool[i])
{
if((j >= this.range_up) && (j <= this.range_down))
{
var r = this.rows[j] = a.rows[k++];
r.className = "";
var c = r.firstChild;
c.innerHTML = this.parse(this.keywords[i], plen, re);
c.setAttribute("pos", j);
}
if(++j > this.range_down) break;
}
}
if(kl > this.entry_limit)
{
var b = (j < this.total);
adjustArrow(this, false, b, this.funcDown);
}
if(this.msie)
{
var helper = document.getElementById("suggest_helper_" + this.field_id);
if(helper) helper.style.width = a.parentNode.offsetWidth + 2;
}
},
reposition: function()
{
if(this.displayed)
{
this.cur_y = this.curPos(this.field, "Top") + this.field.offsetHeight;
this.cur_x = this.curPos(this.field, "Left");
var control = document.getElementById("suggest_table_" + this.field_id);
control.style.top = this.cur_y + "px";
control.style.left = this.cur_x + "px";
}
},
startTimer: function(on_list)
{
if(this.time_out > 0)
this.timer = setTimeout(function() { this.mouse_on_list = on_list; this.hide(); }, this.time_out);
},
stopTimer: function()
{
if(this.timer)
{
clearTimeout(this.timer);
this.timer = 0;
}
},
getRow: function(index)
{
if(typeof(index) == "undefined") index = this.current;
return (this.rows[index] || null);
},
fixArrows: function(base)
{
if(this.total <= this.entry_limit) return;
var table = base.firstChild, at_start = (this.current == 0), at_end = (this.current == (this.total - 1));
var row = table.rows[0];
row.className = at_start ? "up-disabled" : "up";
row = table.rows[this.entry_limit + 1];
row.className = at_end ? "down-disabled" : "down";
},
scroll: function(direction, times)
{
if(!this.displayed) return;
this.field.focus();
if(this.current == (direction ? 0 : (this.total - 1))) return;
if(!direction && (this.current < 0))
{
this.current = -1;
}
else
{
var t = this.getRow();
if(t && t.style)
t.className = "";
}
this.current += times * (direction ? -1 : 1);
if(direction)
{
if(this.current < 0)
this.current = 0;
}
else
{
if(this.current >= this.total)
this.current = this.total - 1;
if(this.use_scroll && (this.current >= this.rows.length))
this.current = this.rows.length - 1;
}
var t = this.getRow(), base = document.getElementById("suggest_table_" + this.field_id);
if(this.use_scroll)
{
if(direction)
{
if(t.offsetTop < base.scrollTop)
base.scrollTop = t.offsetTop;
}
else
{
if((t.offsetTop + t.offsetHeight) > (base.scrollTop + base.offsetHeight))
{
var ndx = this.current - this.entry_limit + 1;
if(ndx > 0)
base.scrollTop = this.getRow(ndx).offsetTop;
}
}
}
else
{
if(direction)
{
if(this.current < this.range_up)
{
this.range_up -= times;
if(this.range_up < 0) this.range_up = 0;
this.range_down = this.range_up + this.entry_limit - 1;
this.remake();
}
else this.fixArrows(base);
}
else
{
if(this.current > this.range_down)
{
this.range_down += times;
if(this.range_down > (this.total - 1)) this.range_down = this.total - 1;
this.range_up = this.range_down - this.entry_limit + 1;
this.remake();
}
else this.fixArrows(base);
}
t = this.getRow();
}
if(t && t.style)
t.className = "selected";
this.stopTimer();
this.startTimer(1);
this.field.focus();
},
mouseClick: function(event)
{
event = event || window.event;
var elem = event.srcElement || event.target;
if(!elem.id) elem = elem.parentNode;
var obj = elem.autosuggest;
if(!obj)
{
var tag = elem.tagName.toLowerCase();
elem = (tag == "tr") ? elem.firstChild : elem.parentNode;
obj = elem.autosuggest;
}
if(!obj || !obj.displayed) return;
obj.mouse_on_list = 0;
obj.current = parseInt(elem.getAttribute("pos"), 10);
obj.choose();
},
focusTable: function()
{
this.mouse_on_list = 1;
},
unfocusTable: function()
{
this.mouse_on_list = 0;
this.stopTimer();
this.startTimer(0)
},
highlightTable: function(event)
{
event = event || window.event;
var elem = event.srcElement || event.target;
var obj = elem.autosuggest;
if(!obj) return;
obj.mouse_on_list = 1;
var row = obj.getRow();
if(row && row.style)
row.className = "";
obj.current = parseInt(elem.getAttribute("pos"), 10);
row = obj.getRow();
if(row && row.style)
row.className = "selected";
obj.stopTimer();
obj.startTimer(0);
},
choose: function()
{
if(!this.displayed) return;
if(this.current < 0) return;
this.displayed = false;
var kl = this.keywords.length;
for(var i = 0, c = 0; i < kl; i++)
if(this.bool[i] && (c++ == this.current))
break;
this.selectedIndex = i;
this.insertWord(this.keywords[i]);
if(this.onSelect)
this.onSelect(i, this);
},
insertWord: function(a)
{
// Sets the caret position to l in the object
function setCaretPos(obj, l)
{
obj.focus();
if(obj.setSelectionRange)
{
obj.setSelectionRange(l, l);
}
else if(obj.createTextRange)
{
var m = obj.createTextRange();
m.moveStart("character", l);
m.collapse();
m.select();
}
}
if(this.text_delimiter.length > 0)
{
var str = "", word = this.delim_words[this.current_word], wl = word.length, l = 0;
for(var i = 0; i < this.delim_words.length; i++)
{
if(this.current_word == i)
{
var prespace = "", postspace = "", gotbreak = false;
for(var j = 0; j < wl; ++j)
{
if(word.charAt(j) != " ")
{
gotbreak = true;
break;
}
prespace += " ";
}
for(j = wl - 1; j >= 0; --j)
{
if(word.charAt(j) != " ")
break;
postspace += " ";
}
str += prespace;
str += a;
l = str.length;
if(gotbreak) str += postspace;
}
else
{
str += this.delim_words[i];
}
if(i != this.delim_words.length - 1)
str += this.delim_char[i];
}
this.field.value = str;
setCaretPos(this.field, l);
}
else
{
this.field.value = a;
}
this.mouse_on_list = 0;
this.hide();
},
hide: function()
{
if(this.mouse_on_list == 0)
{
this.displayed = false;
var base = document.getElementById("suggest_table_" + this.field_id);
if(base)
{
var helper = document.getElementById("suggest_helper_" + this.field_id);
if(helper)
document.body.removeChild(helper);
document.body.removeChild(base);
}
this.stopTimer();
this.cur_x = 0;
this.cur_y = 0;
this.cur_w = 0;
this.cur_h = 0;
this.rows = [];
}
},
keyPress: function(event)
{
// On firefox there is no way to distingish pressing shift-8 (asterix)
// from pressing 8 during the keyDown event, so we do restrict_typing
// whilest handling the keyPress event
event = event || window.event;
var code = window.event ? event.keyCode : event.charCode;
var obj = event.srcElement || event.target;
obj = obj.autosuggest;
if(obj.restrict_typing && !obj.suggest_url.length && (code >= 32))
{
var caret_pos = obj.getCaretEnd(obj.field);
var new_term = obj.field.value.substr(0, caret_pos).toLowerCase();
var isDelimiter = false;
if(obj.text_delimiter.length > 0)
{
// check whether the pressed key is a delimiter key
var delim_split = "";
for(var j = 0; j < obj.text_delimiter.length; j++)
{
delim_split += obj.text_delimiter[j];
if(obj.text_delimiter[j] == String.fromCharCode(code))
isDelimiter = true;
}
// only consider part of term after last delimiter
delim_split = obj.addSlashes(delim_split);
var lastterm_rx = new RegExp(".*([" + delim_split + "])");
new_term = new_term.replace(lastterm_rx, '');
}
var keyw_len = obj.keywords.length;
var i = 0;
if(isDelimiter)
{
// pressed key is a delimiter: allow if current term is complete
for(i = 0; i < keyw_len; i++)
if(obj.keywords[i].toLowerCase() == new_term)
break;
}
else
{
new_term += String.fromCharCode(code).toLowerCase();
for(i = 0; i < keyw_len; i++)
if(obj.keywords[i].toLowerCase().indexOf(new_term) != -1)
break;
}
if(i == keyw_len)
{
obj.stopEvent(event);
return false;
}
}
if(obj.caret_moved) obj.stopEvent(event);
return !obj.caret_moved;
},
checkKey: function(event)
{
event = event || window.event;
var code = event.keyCode;
var obj = event.srcElement || event.target;
obj = obj.autosuggest;
obj.caret_moved = 0;
var term = "";
obj.stopTimer();
switch(code)
{
// Up arrow:
case 38:
if(obj.current <= 0)
{
obj.stopEvent(event);
obj.hide();
}
else
{
obj.scroll(true, 1);
obj.caret_moved = 1;
obj.stopEvent(event);
}
return false;
// Down arrow:
case 40:
if(!obj.displayed)
{
obj.timer = setTimeout(function()
{
obj.preSuggest(-1);
},
25);
}
else
{
obj.scroll(false, 1);
obj.caret_moved = 1;
}
return false;
// Page up:
case 33:
if(obj.current == 0)
{
obj.caret_moved = 0;
return false;
}
obj.scroll(true, (obj.use_scroll || (obj.getRow() == obj.rows[obj.range_up])) ? obj.entry_limit : (obj.current - obj.range_up));
obj.caret_moved = 1;
break;
// Page down:
case 34:
if(obj.current == (obj.total - 1))
{
obj.caret_moved = 0;
return false;
}
obj.scroll(false, (obj.use_scroll || (obj.getRow() == obj.rows[obj.range_down])) ? obj.entry_limit : (obj.range_down - obj.current));
obj.caret_moved = 1;
break;
// Home
case 36:
if(obj.current == 0)
{
obj.caret_moved = 0;
return false;
}
obj.scroll(true, obj.total);
obj.caret_moved = 1;
break;
// End
case 35:
if(obj.current == (obj.total - 1))
{
obj.caret_moved = 0;
return false;
}
obj.scroll(false, obj.total);
obj.caret_moved = 1;
break;
// Esc:
case 27:
term = obj.field.value;
obj.mouse_on_list = 0;
obj.hide();
break;
// Enter:
case 13:
if(obj.displayed)
{
obj.caret_moved = 1;
obj.choose();
return false;
}
break;
// Tab:
case 9:
if((obj.displayed && (obj.current >= 0)) || obj.timer)
{
obj.caret_moved = 1;
obj.choose();
setTimeout(function() { obj.field.focus(); }, 25);
return false;
}
break;
case 16: //shift
break;
default:
obj.caret_moved = 0;
obj.timer = setTimeout(function()
{
obj.preSuggest(code);
},
(obj.response_time < 10 ? 10 : obj.response_time));
}
if(term.length) setTimeout(function() { obj.field.value = term; }, 25);
return true;
},
preSuggest: function(kc)
{
if(!this.timer)
return;
this.stopTimer();
if(this.displayed && (this.lastterm == this.field.value)) return;
this.lastterm = this.field.value;
if(kc == 38 || kc == 40 || kc == 13) return;
var c = 0;
if(this.displayed && (this.current >= 0))
{
for(var i = 0; i < this.keywords.length; i++)
{
if(this.bool[i]) ++c;
if(c == this.current)
{
this.previous = i;
break;
}
}
}
else
{
this.previous = -1;
}
if(!this.field.value.length && (kc != -1))
{
this.mouse_on_list = 0;
this.hide();
}
var ot, t;
if(this.text_delimiter.length > 0)
{
var caret_pos = this.getCaretEnd(this.field);
var delim_split = "";
for(var i = 0; i < this.text_delimiter.length; i++)
delim_split += this.text_delimiter[i];
delim_split = this.addSlashes(delim_split);
var delim_split_rx = new RegExp("([" + delim_split + "])");
c = 0;
this.delim_words = [];
this.delim_words[0] = "";
for(var i = 0, j = this.field.value.length; i < this.field.value.length; i++, j--)
{
if(this.field.value.substr(i, j).search(delim_split_rx) == 0)
{
var ma = this.field.value.substr(i, j).match(delim_split_rx);
this.delim_char[c++] = ma[1];
this.delim_words[c] = "";
}
else
{
this.delim_words[c] += this.field.value.charAt(i);
}
}
var l = 0;
this.current_word = -1;
for(i = 0; i < this.delim_words.length; i++)
{
if((caret_pos >= l) && (caret_pos <= (l + this.delim_words[i].length)))
this.current_word = i;
l += this.delim_words[i].length + 1;
}
ot = this.trim(this.delim_words[this.current_word]);
t = this.trim(this.addSlashes(this.delim_words[this.current_word]));
}
else
{
ot = this.field.value;
t = this.addSlashes(ot);
}
if(ot.length == 0 && (kc != -1))
{
this.mouse_on_list = 0;
this.hide();
}
else if((ot.length == 1) || this.full_refresh ||
((ot.length > 1) && !this.keywords.length) ||
((ot.length > 1) && (this.keywords[0].charAt(0).toLowerCase() != ot.charAt(0).toLowerCase())))
{
var ot_ = ((ot.length > 1) && !this.full_refresh) ? ot.charAt(0) : ot;
if(this.suggest_url.length)
{
// create xmlhttprequest object:
var http = null;
if(typeof(XMLHttpRequest) != "undefined")
{
try
{
http = new XMLHttpRequest();
}
catch (e) { http = null; }
}
else
{
try
{
http = new ActiveXObject("Msxml2.XMLHTTP") ;
}
catch (e)
{
try
{
http = new ActiveXObject("Microsoft.XMLHTTP") ;
}
catch (e) { http = null; }
}
}
if(http)
{
// Uncomment for local debugging in Mozilla/Firefox:
// try { netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead"); } catch (e) { }
if(http.overrideMimeType)
http.overrideMimeType("text/xml");
http.open("GET", this.suggest_url + ot_, true);
var that = this;
http.onreadystatechange = function(n)
{
if(http.readyState == 4)
{
if((http.status == 200) || (http.status == 0))
{
var text = http.responseText;
var index1 = text.indexOf("");
var index2 = (index1 == -1) ? text.length : text.indexOf(" lcs)
{
lcs = table[i+1][j+1];
}
}
else
{
table[i+1][j+1] = 0;
}
}
}
return lcs;
},
// A function to compute the Levenshtein distance between two strings
// Licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported
// Full License can be found at http://creativecommons.org/licenses/by-sa/3.0/legalcode
// This code is an unmodified (aside from style) version of the code written by Marco de Wit
// and was found at http://stackoverflow.com/a/18514751/745719
levenshtein: (function()
{
var row2 = [];
return function(s1, s2)
{
if(s1 === s2)
{
return 0;
}
else
{
var s1_len = s1.length, s2_len = s2.length;
if(s1_len && s2_len)
{
var i1 = 0, i2 = 0, a, b, c, c2, row = row2;
while(i1 < s1_len)
row[i1] = ++i1;
while(i2 < s2_len)
{
c2 = s2.charCodeAt(i2);
a = i2;
++i2;
b = i2;
for(i1 = 0; i1 < s1_len; ++i1)
{
c = a + (s1.charCodeAt(i1) !== c2 ? 1 : 0);
a = row[i1];
b = b < a ? (b < c ? b + 1 : c) : (a < c ? a + 1 : c);
row[i1] = b;
}
}
return b;
}
else
{
return s1_len + s2_len;
}
}
};
})(),
// Setup an event handler for the given event and DOM element
// event_name refers to the event trigger, without the "on", like click or mouseover
// func_name refers to the function callback that is invoked when event is triggered
addEvent: function(obj, event_name, func_ref)
{
if(obj.addEventListener && !window.opera)
{
obj.addEventListener(event_name, func_ref, true);
}
else if(obj.attachEvent)
{
obj.attachEvent("on" + event_name, func_ref)
}
else
{
obj["on" + event_name] = func_ref;
}
},
// Stop an event from bubbling up the event DOM
stopEvent: function(event)
{
event = event || window.event;
if(event)
{
if(event.stopPropagation) event.stopPropagation();
if(event.preventDefault) event.preventDefault();
if(typeof(event.cancelBubble) != "undefined")
{
event.cancelBubble = true;
event.returnValue = false;
}
}
return false;
},
// Get the end position of the caret in the object. Note that the obj needs to be in focus first.
getCaretEnd: function(obj)
{
if(typeof(obj.selectionEnd) != "undefined")
{
return obj.selectionEnd;
}
else if(document.selection && document.selection.createRange)
{
var M = document.selection.createRange(), Lp;
try
{
Lp = M.duplicate();
Lp.moveToElementText(obj);
}
catch(e)
{
Lp = obj.createTextRange();
}
Lp.setEndPoint("EndToEnd", M);
var rb = Lp.text.length;
if(rb > obj.value.length)
return -1;
return rb;
}
return -1;
},
// Get offset position from the top/left of the screen:
curPos: function(obj, what)
{
var coord = 0;
while(obj)
{
coord += obj["offset" + what];
obj = obj.offsetParent;
}
return coord;
},
// String functions:
addSlashes: function(str) { return str.replace(/(["\\\.\|\[\]\^\*\+\?\$\(\)])/g, "\\$1"); },
trim: function(str) { return str.replace(/^\s*(\S*(\s+\S+)*)\s*$/, "$1"); }
};