src/hg/js/jquery.autocomplete.js 1.1

1.1 2010/02/04 22:47:57 larrym
version 1.4.2 downloaded from http://code.google.com/p/jqac/
Index: src/hg/js/jquery.autocomplete.js
===================================================================
RCS file: src/hg/js/jquery.autocomplete.js
diff -N src/hg/js/jquery.autocomplete.js
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ src/hg/js/jquery.autocomplete.js	4 Feb 2010 22:47:57 -0000	1.1
@@ -0,0 +1,481 @@
+/**
+ * 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('&','&amp;').
+           replace('<','&lt;').
+           replace('>','&gt;');
+}
+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);
+  };
+
+})};
+
+})($);