src/hg/js/jquery.autocomplete.js 1.2
1.2 2010/02/04 22:51:49 larrym
patch which fixes following problem: up/down arrows didn't work on IE, Safari and Chrome; patch downloaded from: http://code.google.com/p/jqac/issues/detail?id=1
Index: src/hg/js/jquery.autocomplete.js
===================================================================
RCS file: /projects/compbio/cvsroot/kent/src/hg/js/jquery.autocomplete.js,v
retrieving revision 1.1
retrieving revision 1.2
diff -b -B -U 1000000 -r1.1 -r1.2
--- src/hg/js/jquery.autocomplete.js 4 Feb 2010 22:47:57 -0000 1.1
+++ src/hg/js/jquery.autocomplete.js 4 Feb 2010 22:51:49 -0000 1.2
@@ -1,481 +1,473 @@
/**
* Extending jQuery with autocomplete
* Version: 1.4.2
* Author: Yanik Gleyzer (clonyara)
*/
(function($) {
// some key codes
var RETURN = 13;
var TAB = 9;
var ESC = 27;
var ARRLEFT = 37;
var ARRUP = 38;
var ARRRIGHT = 39;
var ARRDOWN = 40;
var BACKSPACE = 8;
var DELETE = 46;
function debug(s){
$('#info').append(htmlspecialchars(s)+'<br>');
}
// getting caret position obj: {start,end}
function getCaretPosition(obj){
var start = -1;
var end = -1;
if(typeof obj.selectionStart != "undefined"){
start = obj.selectionStart;
end = obj.selectionEnd;
}
else if(document.selection&&document.selection.createRange){
var M=document.selection.createRange();
var Lp;
try{
Lp = M.duplicate();
Lp.moveToElementText(obj);
}catch(e){
Lp=obj.createTextRange();
}
Lp.setEndPoint("EndToStart",M);
start=Lp.text.length;
if(start>obj.value.length)
start = -1;
Lp.setEndPoint("EndToStart",M);
end=Lp.text.length;
if(end>obj.value.length)
end = -1;
}
return {'start':start,'end':end};
}
// set caret to
function setCaret(obj,l){
obj.focus();
if (obj.setSelectionRange){
obj.setSelectionRange(l,l);
}
else if(obj.createTextRange){
m = obj.createTextRange();
m.moveStart('character',l);
m.collapse();
m.select();
}
}
// prepare array with velued objects
// required properties are id and value
// rest of properties remaines
function prepareArray(jsondata){
var new_arr = [];
for(var i=0;i<jsondata.length;i++){
if(jsondata[i].id != undefined && jsondata[i].value != undefined){
jsondata[i].id = jsondata[i].id+"";
jsondata[i].value = jsondata[i].value+"";
if(jsondata[i].info != undefined)
jsondata[i].info = jsondata[i].info+"";
new_arr.push(jsondata[i]);
}
}
return new_arr;
}
// php analogs
function escapearg(s){
if(s == undefined || !s) return '';
return s.replace('\\','\\\\').
replace('*','\\*').
replace('.','\\.').
replace('/','\\/');
}
function htmlspecialchars(s){
if(s == undefined || !s) return '';
return s.replace('&','&').
replace('<','<').
replace('>','>');
}
function ltrim(s){
if(s == undefined || !s) return '';
return s.replace(/^\s+/g,'');
}
// extending jQuery
$.fn.autocomplete = function(options){ return this.each(function(){
// take me
var me = $(this);
var me_this = $(this).get(0);
// test for supported text elements
if(!me.is('input:text,input:password,textarea'))
return;
// get or ajax_get required!
if(!options && (!$.isFunction(options.get) || !options.ajax_get)){
return;
}
// check plugin enabled
if(me.attr('jqac') == 'on') return;
// plugin on!
me.attr('jqac','on');
// no browser's autocomplete!
me.attr('autocomplete','off');
// default options
options = $.extend({
delay : 500 ,
timeout : 5000 ,
minchars : 3 ,
multi : false ,
cache : true ,
height : 150 ,
autowidth : false ,
noresults : 'No results'
},
options);
// bind key events
// handle special keys here
me.keydown(function(ev){
switch(ev.which){
// return choose highlighted item or default propogate
case RETURN:
if(!suggestions_menu) return true;
else setHighlightedValue();
return false;
// escape clears menu
case ESC:
clearSuggestions();
return false;
- }
- return true;
- });
- me.keypress(function(ev){
- // ev.which doesn't work here - it always returns 0
- switch(ev.keyCode){
- case RETURN: case ESC:
- return false;
// up changes highlight
case ARRUP:
changeHighlight(ev.keyCode);
return false;
// down changes highlight or open new menu
case ARRDOWN:
if(!suggestions_menu) getSuggestions(getUserInput());
else changeHighlight(ev.keyCode);
return false;
}
return true;
});
// handle normal characters here
me.keyup(function(ev) {
switch(ev.which) {
case RETURN: case ESC: case ARRLEFT: case ARRRIGHT: case ARRUP: case ARRDOWN:
return false;
default:
getSuggestions(getUserInput());
}
return true;
});
// init variables
var user_input = "";
var input_chars_size = 0;
var suggestions = [];
var current_highlight = 0;
var suggestions_menu = false;
var suggestions_list = false;
var loading_indicator = false;
var clearSuggestionsTimer = false;
var getSuggestionsTimer = false;
var showLoadingTimer = false;
var zIndex = me.css('z-index');
// get user input
function getUserInput(){
var val = me.val();
if(options.multi){
var pos = getCaretPosition(me_this);
var start = pos.start;
for(;start>0 && val.charAt(start-1) != ',';start--){}
var end = pos.start;
for(;end<val.length && val.charAt(end) != ',';end++){}
var val = val.substr(start,end-start);
}
return ltrim(val);
}
// set suggestion
function setSuggestion(val){
user_input = val;
if(options.multi){
var orig = me.val();
var pos = getCaretPosition(me_this);
var start = pos.start;
for(;start>0 && orig.charAt(start-1) != ',';start--){}
var end = pos.start;
for(;end<orig.length && orig.charAt(end) != ',';end++){}
var new_val = orig.substr(0,start) + (start>0?' ':'') + val + orig.substr(end);
me.val(new_val);
setCaret(me_this,start + val.length + (start>0?1:0));
}
else{
me_this.focus();
me.val(val);
}
}
// get suggestions
function getSuggestions(val){
// input length is less than the min required to trigger a request
// reset input string
// do nothing
if (val.length < options.minchars){
clearSuggestions();
return false;
}
// if caching enabled, and user is typing (ie. length of input is increasing)
// filter results out of suggestions from last request
if (options.cache && val.length > input_chars_size && suggestions.length){
var arr = [];
for (var i=0;i<suggestions.length;i++){
var re = new RegExp("("+escapearg(val)+")",'ig');
if(re.exec(suggestions[i].value))
arr.push( suggestions[i] );
}
user_input = val;
input_chars_size = val.length;
suggestions = arr;
createList(suggestions);
return false;
}
else{// do new request
clearTimeout(getSuggestionsTimer);
user_input = val;
input_chars_size = val.length;
getSuggestionsTimer = setTimeout(
function(){
suggestions = [];
// call pre callback, if exists
if($.isFunction(options.pre_callback))
options.pre_callback();
// call get
if($.isFunction(options.get)){
suggestions = prepareArray(options.get(val));
createList(suggestions);
}
// call AJAX get
else if($.isFunction(options.ajax_get)){
clearSuggestions();
showLoadingTimer = setTimeout(show_loading,options.delay);
options.ajax_get(val,ajax_continuation);
}
},
options.delay );
}
return false;
};
// AJAX continuation
function ajax_continuation(jsondata){
hide_loading();
suggestions = prepareArray(jsondata);
createList(suggestions);
}
// shows loading indicator
function show_loading(){
if(!loading_indicator){
loading_indicator = $('<div class="jqac-menu"><div class="jqac-loading">Loading</div></div>').get(0);
$(loading_indicator).css('position','absolute');
var pos = me.offset();
$(loading_indicator).css('left', pos.left + "px");
$(loading_indicator).css('top', ( pos.top + me.height() + 2 ) + "px");
if(!options.autowidth)
$(loading_indicator).width(me.width());
$('body').append(loading_indicator);
}
$(loading_indicator).show();
setTimeout(hide_loading,10000);
}
// hides loading indicator
function hide_loading(){
if(loading_indicator)
$(loading_indicator).hide();
clearTimeout(showLoadingTimer);
}
// create suggestions list
function createList(arr){
if(suggestions_menu)
$(suggestions_menu).remove();
hide_loading();
killTimeout();
// create holding div
suggestions_menu = $('<div class="jqac-menu"></div>').get(0);
// ovveride some necessary CSS properties
$(suggestions_menu).css({'position':'absolute',
'z-index':zIndex,
'max-height':options.height+'px',
'overflow-y':'auto'});
// create and populate ul
suggestions_list = $('<ul></ul>').get(0);
// set some CSS's
$(suggestions_list).
css('list-style','none').
css('margin','0px').
css('padding','2px').
css('overflow','hidden');
// regexp for replace
var re = new RegExp("("+escapearg(htmlspecialchars(user_input))+")",'ig');
// loop throught arr of suggestions creating an LI element for each suggestion
for (var i=0;i<arr.length;i++){
var val = new String(arr[i].value);
// using RE
var output = htmlspecialchars(val).replace(re,'<em>$1</em>');
// using substr
//var st = val.toLowerCase().indexOf( user_input.toLowerCase() );
//var len = user_input.length;
//var output = val.substring(0,st)+"<em>"+val.substring(st,st+len)+"</em>"+val.substring(st+len);
var span = $('<span class="jqac-link">'+output+'</span>').get(0);
if (arr[i].info != undefined && arr[i].info != ""){
$(span).append($('<div class="jqac-info">'+arr[i].info+'</div>'));
}
$(span).attr('name',i+1);
$(span).click(function () { setHighlightedValue(); });
$(span).mouseover(function () { setHighlight($(this).attr('name'),true); });
var li = $('<li></li>').get(0);
$(li).append(span);
$(suggestions_list).append(li);
}
// no results
if (arr.length == 0){
$(suggestions_list).append('<li class="jqac-warning">'+options.noresults+'</li>');
}
$(suggestions_menu).append(suggestions_list);
// get position of target textfield
// position holding div below it
// set width of holding div to width of field
var pos = me.offset();
$(suggestions_menu).css('left', pos.left + "px");
$(suggestions_menu).css('top', ( pos.top + me.height() + 2 ) + "px");
if(!options.autowidth)
$(suggestions_menu).width(me.width());
// set mouseover functions for div
// when mouse pointer leaves div, set a timeout to remove the list after an interval
// when mouse enters div, kill the timeout so the list won't be removed
$(suggestions_menu).mouseover(function(){ killTimeout() });
$(suggestions_menu).mouseout(function(){ resetTimeout() });
// add DIV to document
$('body').append(suggestions_menu);
// bgIFRAME support
if($.fn.bgiframe)
$(suggestions_menu).bgiframe({height: suggestions_menu.scrollHeight});
// adjust height: add +20 for scrollbar
if(suggestions_menu.scrollHeight > options.height){
$(suggestions_menu).height(options.height);
$(suggestions_menu).width($(suggestions_menu).width()+20);
}
// currently no item is highlighted
current_highlight = 0;
// remove list after an interval
clearSuggestionsTimer = setTimeout(function () { clearSuggestions() }, options.timeout);
};
// set highlighted value
function setHighlightedValue(){
if(current_highlight && suggestions[current_highlight-1]){
var sugg = suggestions[ current_highlight-1 ];
if(sugg.affected_value != undefined && sugg.affected_value != '')
setSuggestion(sugg.affected_value);
else
setSuggestion(sugg.value);
// pass selected object to callback function, if exists
if ($.isFunction(options.callback))
options.callback( suggestions[current_highlight-1] );
clearSuggestions();
}
};
// change highlight according to key
function changeHighlight(key){
if(!suggestions_list || suggestions.length == 0) return false;
var n;
if (key == ARRDOWN)
n = current_highlight + 1;
else if (key == ARRUP)
n = current_highlight - 1;
if (n > $(suggestions_list).children().size())
n = 1;
if (n < 1)
n = $(suggestions_list).children().size();
setHighlight(n);
};
// change highlight
function setHighlight(n,mouse_mode){
if (!suggestions_list) return false;
if (current_highlight > 0) clearHighlight();
current_highlight = Number(n);
var li = $(suggestions_list).children().get(current_highlight-1);
li.className = 'jqac-highlight';
// for mouse mode don't adjust scroll! prevent scrolling jumps
if(!mouse_mode) adjustScroll(li);
killTimeout();
};
// clear highlight
function clearHighlight(){
if (!suggestions_list)return false;
if (current_highlight > 0){
$(suggestions_list).children().get(current_highlight-1).className = '';
current_highlight = 0;
}
};
// clear suggestions list
function clearSuggestions(){
killTimeout();
if(suggestions_menu){
$(suggestions_menu).remove();
suggestions_menu = false;
suggestions_list = false;
current_highlight = 0;
}
};
// set scroll
function adjustScroll(el){
if(!suggestions_menu) return false;
var viewportHeight = suggestions_menu.clientHeight;
var wholeHeight = suggestions_menu.scrollHeight;
var scrolled = suggestions_menu.scrollTop;
var elTop = el.offsetTop;
var elBottom = elTop + el.offsetHeight;
if(elBottom > scrolled + viewportHeight){
suggestions_menu.scrollTop = elBottom - viewportHeight;
}
else if(elTop < scrolled){
suggestions_menu.scrollTop = elTop;
}
return true;
}
// timeout funcs
function killTimeout(){
clearTimeout(clearSuggestionsTimer);
};
function resetTimeout(){
clearTimeout(clearSuggestionsTimer);
clearSuggestionsTimer = setTimeout(function () { clearSuggestions() }, 1000);
};
})};
})($);