/* Auto-suggest control, version 2.3, August 18th 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)
{
// "Public" variables:
this.parameters = new Array(); // Added by Jason 8/25/2009. Stores parameters to send with the ajax query string.
// Added by Jason 9/5/2009.
this.use_dropdown_button = false;
this.filter_suggestions = true; // Indicates whether the suggestions are filtered based on the value input.
this.read_only = false;
this.fill_visible = true; // Indicates whether the suggestion div is to be visible while building.
// End of addition.
this.time_out = 0; // autocomplete timeout, in milliseconds (0: autocomplete never times out)
this.response_time = 500; // 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 = false; // 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 = true; // 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
// Styles:
this.bg_color = "#FFFFF0"; // background color for the suggestion list
this.ar_color = "#656291"; // background color for the "arrow" rows (used if use_scroll is false)
this.text_color = "#000"; // text color for the non-selected suggestions
this.htext_color = "#F00"; // text color for the selected suggestion
this.hcolor = "#D6D7E7"; // background color for the selected suggestion
this.font = "verdana,arial,helvetica"; // font(s) of suggestion items
this.font_size = "10px"; // font size of suggestion items
// "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 = document.getElementById(id);
this.lastterm = this.field.value;
this.keywords = [], this.keywords_init = [];
this.values = [], this.values_init = [];
// Changed by Jason 9/7/2009: Added drop-down button images.
this.image = ["arrow-down.gif", "arrow-down-d.gif", "arrow-up.gif", "arrow-up-d.gif", "drop-down.png", "drop-down_h.png", "drop-down_p.png"];
// End of change.
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;
// Precache arrows: Changed by Jason 9/7/2009: Also precaching drop-down button.
if(!this.use_scroll)
{
for(var i = 0; i < 7; i++) //
{
var img = new Image;
img.src = this.image[i];
}
}
// 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));
// Added by Jason 9/5/2009:
var that = this;
setTimeout(function()
{
if (that.use_dropdown_button)
that.addDropDownButton(); // Display drop-down button.
if (that.read_only)
{
that.field.setAttribute("readonly", "readonly"); // Set read-only.
that.field.style.cursor = "default";
that.field.onclick = function() { that.show(false); };
that.fillAndSelect();
that.parameterChangeFill();
}
}, 50);
// End of Addition
return this;
},
// Added by Jason 8/25/2009. Returns the parameter string to send as querystring for ajax call.
getParameterString: function()
{
var parameterString = "";
var name = "";
var value = "";
for (var index = 0; index < this.parameters.length; index++)
{
name = this.parameters[index][0];
value = this.parameters[index][1];
if (name.substring(0, 1) == "@")
{
var el = document.getElementById(name.substring(1, name.length));
name = value;
if (el)
value = el.value;
}
if (parameterString.length > 0)
parameterString = parameterString + "&" + name + "=" + value;
else
parameterString = parameterString + name + "=" + value;
}
return parameterString;
},
// Added by Jason 9/4/2009: Display drop-down button.
addDropDownButton: function()
{
var originalWidth = this.field.style.width.replace("px", "");
var newWidth = originalWidth - 18;
var dropDownButton = document.createElement("img");
dropDownButton.setAttribute("src", this.image[4]);
dropDownButton.style.position = "absolute";
var left = this.browserAdjustLeft(this.curPos(this.field, "Left") + this.field.offsetWidth - 20);
var top = this.browserAdjustTop(this.curPos(this.field, "Top") + 2);
dropDownButton.setAttribute("style", "position: absolute; top: " + top + "px; left: " + left + "px;");
this.field.parentNode.insertBefore(dropDownButton, this.field.nextSibling);
dropDownButton.autosuggest = this;
var that = this;
dropDownButton.onclick = function() { that.show(false); };
dropDownButton.onmousedown = function() { dropDownButton.setAttribute("src", that.image[6]); };
dropDownButton.onmouseup = function() { dropDownButton.setAttribute("src", that.image[4]); };
dropDownButton.onmouseover = function() { dropDownButton.setAttribute("src", that.image[5]); };
dropDownButton.onmouseout = function() { dropDownButton.setAttribute("src", that.image[4]); };
},
show: function(hidden)
{
this.field.focus();
this.timer = 1;
this.preSuggest(-1, hidden);
var that = this;
},
fillAndSelect: function()
{
this.show(true);
},
select: function()
{
if (this.field.value == "" && this.keywords.length)
{
this.field.value = this.trim(this.keywords[0]);
return;
}
else
{
var containsValue = false;
for (var keywordIndex = 0; keywordIndex < this.keywords.length; keywordIndex++)
{
if (this.trim(this.keywords[keywordIndex]) == this.field.value)
{
containsValue = true;
this.selectIndex(keywordIndex, false);
break;
}
}
if (!containsValue)
{
this.selectIndex(0, true);
}
}
if (this.field.onchange) this.field.onchange();
},
// Added by Jason 9/7/2009: Assigning values to server control's hidden
// fields and optionally setting the field value.
selectIndex: function (index, setFieldValue)
{
var selectedValueField = document.getElementsByName(this.field.name + "$SelectedValue");
var selectedTextField = document.getElementsByName(this.field.name + "$SelectedText");
var selectedIndexField = document.getElementsByName(this.field.name + "$SelectedIndex");
if (index >= this.keywords.length)
index = this.keywords.length - 1;
this.selectedIndex = index;
this.current = index;
if (setFieldValue)
{
if (index >= 0)
this.field.value = this.trim(this.keywords[index]);
else
this.field.value = "";
}
if (selectedValueField)
{
if (index >= 0)
selectedValueField[0].value = this.trim(this.values[index]);
else
selectedValueField[0].value = "";
}
if (selectedTextField)
{
if (index >= 0)
selectedTextField[0].value = this.trim(this.keywords[index]);
else
selectedTextField[0].value = "";
}
if (selectedIndexField)
{
if (index >= 0)
selectedIndexField[0].value = index;
else
selectedIndexField[0].value = -1;
}
},
selectKeyCode: function(code, setFieldValue)
{
var curWord = this.trim(this.keywords[this.selectedIndex]).toLowerCase();
var curCharacter = curWord.substring(0, 1);
var character = String.fromCharCode(code).toLowerCase();
var selectedIndex = -1;
if (curCharacter != character)
{
for (var index = 0; index < this.keywords.length; index++)
{
if (this.trim(this.keywords[index]).toLowerCase().substring(0, 1) == character)
{
selectedIndex = index;
break;
}
}
}
else
{
var selected = false;
for (var index = this.selectedIndex + 1; index < this.keywords.length; index++)
{
if (this.trim(this.keywords[index]).toLowerCase().substring(0, 1) == character)
{
selectedIndex = index;
selected = true;
break;
}
}
if (!selected)
{
for (var index = 0; index < this.selectedIndex; index++)
{
if (this.trim(this.keywords[index]).toLowerCase().substring(0, 1) == character)
{
selectedIndex = index;
break;
}
}
}
}
if (selectedIndex != -1)
{
if (this.displayed)
{
document.getElementById("suggest_table_" + this.field_id).scrollTop = 0;
this.scroll(false, selectedIndex - this.current);
}
this.selectIndex(selectedIndex, true);
}
},
parameterChangeFill: function ()
{
if (this.parameters.length)
{
var that = this;
for (var parameterIndex = 0; parameterIndex < this.parameters.length; parameterIndex++)
{
var name = this.parameters[parameterIndex][0];
if (name.substring(0, 1) == "@")
{
var el = document.getElementById(name.substring(1, name.length));
if (el)
el.onchange = function () { that.fillAndSelect(); };
}
}
}
},
// Added by Jason 9/5/2009: Typing restriction for AJAX.
ajaxRestrict: function(hidden)
{
var text = this.trim(this.field.value);
var newValue = "";
var containsNewValue = false;
for (var charIndex = 0; charIndex < text.length; charIndex++)
{
for (var wordIndex = 0; wordIndex < this.keywords.length; wordIndex++)
{
if (this.trim(this.keywords[wordIndex]).toLowerCase().indexOf(text.substring(0, charIndex + 1).toLowerCase(), 0) > -1)
{
newValue = text.substring(0, charIndex + 1);
containsNewValue = true;
break;
}
else
containsNewValue = false;
}
if (!containsNewValue)
break;
}
if (!containsNewValue)
{
this.field.value = newValue;
this.show(hidden);
}
},
browserAdjustLeft: function(left)
{
switch (BrowserName())
{
case "Firefox":
case "Opera":
case "Chrome":
case "Safari":
left = left + 1;
break;
}
return left;
},
browserAdjustTop: function(top)
{
switch (BrowserName())
{
case "Chrome":
case "Safari":
top = top + 1;
break;
}
return top;
},
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.body.scrollLeft || 0);
var y = this.mouse_y + (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);
tobuild[c++] = n.substr(0, p);
if (this.filter_suggestions)
tobuild[c++] = "<u><font face=\"";
else
tobuild[c++] = "<font face=\"";
tobuild[c++] = this.font;
tobuild[c++] = "\">";
tobuild[c++] = n.substring(p, plen + p);
if (this.filter_suggestions)
tobuild[c++] = "</font></u>";
else
tobuild[c++] = "</font>";
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.style.position = "absolute";
bb.style.border = "#000000 solid 1px";
bb.style.zIndex = 100;
this.cur_y = this.curPos(this.field, "Top") + this.field.offsetHeight;
bb.style.top = this.browserAdjustTop(this.cur_y - 2) + "px"; // Changed by Jason 9/5/2009: Adjusted placement to mimic select drop-down.
this.cur_x = this.curPos(this.field, "Left");
bb.style.left = this.browserAdjustLeft(this.cur_x + 2) + "px"; // Changed by Jason 9/5/2009: Adjusted placement to mimic select drop-down.
this.cur_w = this.field.offsetWidth - (this.msie ? 2 : 6);
bb.style.width = this.cur_w - 5 + "px"; // Changed by Jason 9/5/2009: Adjusted placement to mimic select drop-down.
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:\"<html></html>\";";
cc.scrolling = "no";
cc.frameBorder = "no";
cc.style.display = "block";
cc.style.position = "absolute";
cc.style.zIndex = 99;
cc.style.filter = "progid:DXImageTransform.Microsoft.Alpha(opacity=0)";
}
var that = this;
var showFull = (this.total > this.entry_limit);
if(this.use_scroll && showFull)
{
this.cur_h = Math.ceil(this.entry_limit * parseInt(this.font_size));
bb.style.height = this.cur_h + "px";
bb.style.overflow = "auto";
bb.style.overflowX = "hidden";
}
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;
str[cn++] = "<table cellspacing=\"1px\" cellpadding=\"2px\" style=\"width:100%;background-color:";
str[cn++] = this.bg_color;
str[cn++] = "\" id=\"suggest_table2_";
str[cn++] = this.field_id;
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(_img)
{
var row = table.insertRow(-1), rs = row.style;
rs.backgroundColor = that.ar_color;
var cell = row.insertCell(0), cs = cell.style;
cs.cursor = "default";
cs.textAlign = "center";
var img = cell.appendChild(document.createElement("img"));
img.src = that.image[_img];
real_height += row.offsetHeight + 1;
return cell;
}
if(!this.use_scroll && showFull)
createArrowRow(3);
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");
// Added by Jason 9/6/2009: Declaring variables for selecting field value match.
var selectedFound = false;
var offsetIndex = 0;
// End of additon.
function addSuggestion(index, _first)
{
var row = that.rows[j] = table.insertRow(-1);
row.style.backgroundColor = (_first || (that.previous == index)) ? that.hcolor : that.bg_color;
var cell = row.insertCell(0), cs = cell.style;
cs.color = (_first || (that.previous == index)) ? that.htext_color : that.text_color;
cs.fontFamily = that.font;
cs.fontSize = that.font_size;
cs.whiteSpace = "nowrap";
cell.innerHTML = that.parse(that.keywords[index], plen, re);
// Added by Jason 9/6/2009: Selecting field value match.
if (that.trim(that.keywords[index]) == that.trim(that.field.value))
{
selectedFound = true;
row.style.backgroundColor = that.hcolor;
cs.color = that.htext_color;
that.previous = index;
that.current = index;
that.selectIndex(index, false);
}
if (selectedFound)
{
offsetIndex++;
if (offsetIndex == that.entry_limit || index == that.keywords.length - 1)
{
bb.scrollTop = cell.offsetTop;
selectedFound = false;
}
}
// End of additon.
cell.setAttribute("pos", j++);
cell.autosuggest = that;
if(that.use_mouse)
{
cs.cursor = this.msie ? "hand" : "pointer";
that.addEvent(cell, "click", that.funcClick);
cell.onmouseover = that.funcHighlight;
}
if (index != that.keywords.length - 1 && !that.fill_visible)
bb.style.visibility = "hidden";
else
bb.style.visibility = "visible";
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(!this.use_scroll && showFull)
{
var cell = createArrowRow(0);
if(this.use_mouse)
{
cell.style.cursor = this.msie ? "hand" : "pointer";
this.addEvent(cell, "click", this.funcDown);
}
}
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);
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, 1);
},
1);
}
},
remake: function()
{
this.rows = [];
var a = document.getElementById("suggest_table2_" + this.field_id);
var k = 0, first = true;
function adjustArrow(obj, img, cond, handler)
{
var hand = obj.msie ? "hand" : "pointer";
var r = a.rows[k++];
r.style.backgroundColor = obj.ar_color;
var c = r.firstChild;
c.style.cursor = "default";
c.style.textAlign = "center";
c.firstChild.src = obj.image[img];
if(cond && handler && obj.use_mouse)
{
obj.addEvent(c, "click", handler);
c.style.cursor = hand;
}
}
if(this.total > this.entry_limit)
{
var b = (this.range_up > 0);
adjustArrow(this, b ? 2 : 3, 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.style.backgroundColor = this.bg_color;
var c = r.firstChild;
c.style.color = this.text_color;
c.style.fontFamily = this.font;
c.style.fontSize = this.font_size;
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, b ? 0 : 1, 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 hand = (this.msie ? "hand" : "pointer");
var cell = table.rows[0].firstChild;
cell.firstChild.src = this.image[at_start ? 3 : 2];
cell.style.cursor = at_start ? "default" : hand;
cell = table.rows[this.entry_limit + 1].firstChild;
cell.firstChild.src = this.image[at_end ? 1 : 0];
cell.style.cursor = at_end ? "default" : hand;
},
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.style.backgroundColor = this.bg_color;
t.firstChild.style.color = this.text_color;
}
}
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.style.backgroundColor = this.hcolor;
t.firstChild.style.color = this.htext_color;
}
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.style.backgroundColor = obj.bg_color;
row.firstChild.style.color = obj.text_color;
}
// Added by Jason 9/6/2009: Unhighlighting selected.
row = obj.getRow(obj.previous);
if(row && row.style)
{
row.style.backgroundColor = obj.bg_color;
row.firstChild.style.color = obj.text_color;
}
// End of addittion.
obj.current = parseInt(elem.getAttribute("pos"), 10);
row = obj.getRow();
if(row && row.style)
{
row.style.backgroundColor = obj.hcolor;
row.firstChild.style.color = obj.htext_color;
}
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]);
// Added by Jason 9/7/2009: Assigning values to server control's hidden fields.
this.selectIndex(i, false);
if (this.field.onchange) this.field.onchange();
// End of addition.
if(this.onSelect)
this.onSelect(i, this);
},
insertWord: function(a)
{
a = this.trim(a); // Added by Jason 9/7/2009: Chrome has issues.
// 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:
// Changed by Jason 9/8/2009: Navigate without displaying list
// if read_only and not displayed.
if(!obj.displayed)
{
if (obj.read_only)
{
if (obj.selectedIndex - 1 >= 0)
obj.selectIndex(obj.selectedIndex - 1, true);
}
return false;
}
else
{
if(obj.current <= 0)
{
obj.stopEvent(event);
obj.hide();
}
else
{
obj.scroll(true, 1);
obj.caret_moved = 1;
obj.stopEvent(event);
}
return false;
}
// End of change.
// Down arrow:
case 40:
// Changed by Jason 9/8/2009: Navigate without displaying list
// if read_only and not displayed.
if(!obj.displayed)
{
if (obj.read_only)
{
if (obj.selectedIndex < obj.keywords.length)
obj.selectIndex(obj.selectedIndex + 1, true);
}
else
{
obj.timer = setTimeout(function()
{
obj.preSuggest(-1);
},
25);
}
}
else
{
obj.scroll(false, 1);
obj.caret_moved = 1;
}
// End of change.
return false;
// Page up:
case 33:
// Changed by Jason 9/8/2009: Navigate without displaying list
// if read_only and not displayed.
if (!obj.displayed)
{
if (obj.read_only)
{
var newIndex = obj.selectedIndex - obj.entry_limit;
if (newIndex < 0)
newIndex = 0;
obj.selectIndex(newIndex, true);
}
}
else
{
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;
}
// End of change.
break;
// Page down:
case 34:
// Changed by Jason 9/8/2009: Navigate without displaying list
// if read_only and not displayed.
if (!obj.displayed)
{
if (obj.read_only)
{
var newIndex = obj.selectedIndex + obj.entry_limit;
if (newIndex >= obj.keywords.length)
newIndex = obj.keywords.length - 1;
obj.selectIndex(newIndex, true);
}
}
else
{
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;
}
// End of change.
break;
// Home
case 36:
// Changed by Jason 9/8/2009: Navigate without displaying list
// if read_only and not displayed.
if (!obj.displayed)
{
if (obj.read_only)
obj.selectIndex(0, true);
}
else
{
if(obj.current == 0)
{
obj.caret_moved = 0;
return false;
}
obj.scroll(true, obj.total);
obj.caret_moved = 1;
}
// End of change.
break;
// End
case 35:
// Changed by Jason 9/8/2009: Navigate without displaying list
// if read_only and not displayed.
if (!obj.displayed)
{
if (obj.read_only)
obj.selectIndex(obj.keywords.length - 1, true);
}
else
{
if(obj.current == (obj.total - 1))
{
obj.caret_moved = 0;
return false;
}
obj.scroll(false, obj.total);
obj.caret_moved = 1;
}
// End of change.
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:
// Changed by Jason 9/8/2009: Navigate without displaying list
// if read_only and not displayed or navigate while displaying
// list if read_only.
if (obj.read_only)
obj.selectKeyCode(code, true);
else
{
obj.caret_moved = 0;
obj.timer = setTimeout(function()
{
obj.preSuggest(code);
},
(obj.response_time < 10 ? 10 : obj.response_time));
}
// End of change.
}
if(term.length) setTimeout(function() { obj.field.value = term; }, 25);
return true;
},
// Changed by Jason 9/7/2009: Allow filling the keywords without displaying
// by sending true for the hidden parameter.
preSuggest: function(kc, hidden)
{
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 || this.read_only ||
((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 && (!this.ajaxCalled || this.full_refresh)) // Changed by Jason 9/6/2009: Preventing unnecessary AJAX calls.
{
// 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");
// Changed by Jason 8/25/2009: Changed to send the parameters with the querystring.
if (this.parameters.length == 0)
http.open("GET", this.suggest_url + ot_, true);
else
http.open("GET", this.suggest_url + this.getParameterString(), true);
// End of change.
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("<listdata>");
var index2 = (index1 == -1) ? text.length : text.indexOf("</listdata", index1 + 10);
index1 += (index1 != -1) ? 10 : 1;
var tmpinfo = text.substring(index1, index2);
if(tmpinfo)
{
that.keywords = tmpinfo.split(that.ajax_delimiter);
if(that.item_delimiter && that.item_delimiter.length)
{
var keyword_number = that.keywords.length;
// Added by Jason 9/7/2009: Assigning values to server control's hidden fields.
var matchingAdded = false;
// End of addition.
for(var i = 0; i < keyword_number; i++)
{
var ca = that.keywords[i], comma = ca.indexOf(that.item_delimiter);
if(comma != -1)
{
var ci = ca.split(that.item_delimiter);
that.keywords[i] = that.keywords_init[i] = ci[0];
that.values[i] = that.values_init[i] = ci[1];
}
else
{
that.keywords[i] = that.keywords_init[i] = ca;
that.values[i] = that.values_init[i] = "";
}
// Added by Jason 9/7/2009: Assigning values to server control's hidden fields.
if (that.trim(that.keywords[i]) == that.trim(that.field.value))
{
matchingAdded = true;
that.selectIndex(i, false);
}
// End of addition.
}
// Added by Jason 9/7/2009: Assigning values to server control's hidden fields.
if (!matchingAdded)
{
that.selectIndex(-1, false);
}
// End of addition.
}
if (that.restrict_typing) that.ajaxRestrict(hidden); // Added by Jason 9/5/2009: Typing restriction for AJAX.
if (!hidden) that.suggest(ot_, t); else that.select(); // Changed by Jason 9/7/2009: Do not show if specified.
}
}
}
}
http.send(null);
}
that.ajaxCalled = true;
}
else if (!hidden) this.suggest(ot, t); else this.select(); // Changed by Jason 9/7/2009: Do not show if specified.
}
else if (!hidden) this.suggest(ot, t); else this.select(); // Changed by Jason 9/7/2009: Do not show if specified.
},
suggest: function(ot, t)
{
if(ot.length < this.start_check) return;
var al = this.keywords.length;
this.total = 0, this.rebuild = false;
for(var i = 0; i < al; i++)
{
this.keywords[i] = this.keywords_init[i];
this.values[i] = this.values_init[i];
this.bool[i] = true;
}
if(!this.field.value.length && !this.filter_suggestions)
{
this.total = al;
}
else
{
var re1 = new RegExp(((!this.limit_start && !this.match_first) ? "" : "^") + t, "i");
var re2 = new RegExp(t, "i");
var after = (!this.limit_start && this.match_first);
var matchArray = [], matchVArray = [];
var afterArray = [], afterVArray = [];
var otherArray = [], otherVArray = [];
for(var i = 0; i < al; i++)
{
var key = this.keywords[i];
var value = this.values[i];
if(re1.test(key) || !this.filter_suggestions)
{
++this.total;
matchArray[matchArray.length] = key;
matchVArray[matchVArray.length] = value;
}
else if(after && re2.test(key))
{
++this.total;
afterArray[afterArray.length] = key;
afterVArray[afterVArray.length] = value;
}
else
{
otherArray[otherArray.length] = key;
otherVArray[otherVArray.length] = value;
}
}
this.keywords = matchArray.concat(afterArray).concat(otherArray);
this.values = matchVArray.concat(afterVArray).concat(otherVArray);
for(i = 0; i < al; i++)
this.bool[i] = (i < this.total);
}
if(this.previous != -1)
this.rebuild = true;
if(this.total)
{
this.startTimer(0);
this.build();
}
else this.hide();
},
// Utility methods:
// 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"); }
};
/// <summary>
/// Returns the name of the browser the page is being viewed in.
/// </summary>
/// <returns>
/// IE, Chrome, Opera, Safari, Firefox or Unknown.
/// </returns>
/// <remarks>
/// Tested this function in IE 7.0, Firefox 2.0, Chrome 1.0, Safari 4.0
/// and Opera 9.64.
/// </remarks>
function BrowserName()
{
if (navigator.appName.indexOf("Microsoft Internet Explorer") > -1)
return "IE";
if (navigator.appVersion.indexOf("Chrome") > -1)
return "Chrome";
if (navigator.appName == "Opera")
return "Opera";
if (navigator.appVersion.indexOf("Safari") > -1)
return "Safari";
if (navigator.appVersion.lastIndexOf(";") == navigator.appVersion.lastIndexOf(";"))
return "Firefox";
return "Unknown";
}
if( typeof(Sys) != "undefined" && typeof(Sys.Application) != "undefined" )
Sys.Application.notifyScriptLoaded();