Fixed: autocomplete got broken somehow
authorDan
Sun, 21 Dec 2008 22:06:25 -0500
changeset 787 b0d0d429d8cf
parent 786 eafd171f0474
child 788 ac081c826715
Fixed: autocomplete got broken somehow
includes/clientside/static/acl.js
includes/clientside/static/autofill.js
--- a/includes/clientside/static/acl.js	Sun Dec 21 20:47:32 2008 -0500
+++ b/includes/clientside/static/acl.js	Sun Dec 21 22:06:25 2008 -0500
@@ -9,7 +9,7 @@
   if(IE)
     return true;
   
-  load_component(['l10n', 'messagebox', 'fadefilter', 'template-compiler', 'autofill', 'jquery', 'jquery-ui']);
+  load_component(['l10n', 'messagebox', 'fadefilter', 'template-compiler', 'jquery', 'jquery-ui', 'autofill']);
   
   if(!page_id || !namespace)
   {
--- a/includes/clientside/static/autofill.js	Sun Dec 21 20:47:32 2008 -0500
+++ b/includes/clientside/static/autofill.js	Sun Dec 21 22:06:25 2008 -0500
@@ -126,628 +126,629 @@
   autofill_init_element(el, {});
 }
 
-addOnloadHook(function()
+window.autofill_init = function()
+{
+  load_component(['l10n', 'jquery', 'jquery-ui']);
+  
+  if ( !window.jQuery )
   {
-    load_component(['l10n', 'jquery', 'jquery-ui']);
-    
-    if ( !window.jQuery )
-    {
-      throw('jQuery didn\'t load properly. Aborting auto-complete init.');
+    throw('jQuery didn\'t load properly. Aborting auto-complete init.');
+  }
+  
+  jQuery.autocomplete = function(input, options) {
+    // Create a link to self
+    var me = this;
+  
+    // Create jQuery object for input element
+    var $input = $(input).attr("autocomplete", "off");
+  
+    // Apply inputClass if necessary
+    if (options.inputClass) {
+      $input.addClass(options.inputClass);
+    }
+  
+    // Create results
+    var results = document.createElement("div");
+    $(results).addClass('tblholder').css('z-index', getHighestZ() + 1).css('margin-top', 0);
+    $(results).css('clip', 'rect(0px,auto,auto,0px)').css('overflow', 'auto').css('max-height', '300px');
+  
+    // Create jQuery object for results
+    // var $results = $(results);
+    var $results = $(results).hide().addClass(options.resultsClass).css("position", "absolute");
+    if( options.width > 0 ) {
+      $results.css("width", options.width);
     }
-    
-    jQuery.autocomplete = function(input, options) {
-      // Create a link to self
-      var me = this;
-    
-      // Create jQuery object for input element
-      var $input = $(input).attr("autocomplete", "off");
-    
-      // Apply inputClass if necessary
-      if (options.inputClass) {
-        $input.addClass(options.inputClass);
-      }
-    
-      // Create results
-      var results = document.createElement("div");
-      $(results).addClass('tblholder').css('z-index', getHighestZ() + 1).css('margin-top', 0);
-      $(results).css('clip', 'rect(0px,auto,auto,0px)').css('overflow', 'auto').css('max-height', '300px');
-    
-      // Create jQuery object for results
-      // var $results = $(results);
-      var $results = $(results).hide().addClass(options.resultsClass).css("position", "absolute");
-      if( options.width > 0 ) {
-        $results.css("width", options.width);
+  
+    // Add to body element
+    $("body").append(results);
+  
+    input.autocompleter = me;
+  
+    var timeout = null;
+    var prev = "";
+    var active = -1;
+    var cache = {};
+    var keyb = false;
+    // hasFocus was false by default, see if making it true helps
+    var hasFocus = true;
+    var hasNoResults = false;
+    var lastKeyPressCode = null;
+    var mouseDownOnSelect = false;
+    var hidingResults = false;
+  
+    // flush cache
+    function flushCache(){
+      cache = {};
+      cache.data = {};
+      cache.length = 0;
+    };
+  
+    // flush cache
+    flushCache();
+  
+    // if there is a data array supplied
+    if( options.data != null ){
+      var sFirstChar = "", stMatchSets = {}, row = [];
+  
+      // no url was specified, we need to adjust the cache length to make sure it fits the local data store
+      if( typeof options.url != "string" ) {
+        options.cacheLength = 1;
       }
-    
-      // Add to body element
-      $("body").append(results);
-    
-      input.autocompleter = me;
-    
-      var timeout = null;
-      var prev = "";
-      var active = -1;
-      var cache = {};
-      var keyb = false;
-      // hasFocus was false by default, see if making it true helps
-      var hasFocus = true;
-      var hasNoResults = false;
-      var lastKeyPressCode = null;
-      var mouseDownOnSelect = false;
-      var hidingResults = false;
-    
-      // flush cache
-      function flushCache(){
-        cache = {};
-        cache.data = {};
-        cache.length = 0;
-      };
-    
-      // flush cache
-      flushCache();
-    
-      // if there is a data array supplied
-      if( options.data != null ){
-        var sFirstChar = "", stMatchSets = {}, row = [];
-    
-        // no url was specified, we need to adjust the cache length to make sure it fits the local data store
-        if( typeof options.url != "string" ) {
-          options.cacheLength = 1;
+  
+      // loop through the array and create a lookup structure
+      for( var i=0; i < options.data.length; i++ ){
+        // if row is a string, make an array otherwise just reference the array
+        row = ((typeof options.data[i] == "string") ? [options.data[i]] : options.data[i]);
+  
+        // if the length is zero, don't add to list
+        if( row[0].length > 0 ){
+          // get the first character
+          sFirstChar = row[0].substring(0, 1).toLowerCase();
+          // if no lookup array for this character exists, look it up now
+          if( !stMatchSets[sFirstChar] ) stMatchSets[sFirstChar] = [];
+          // if the match is a string
+          stMatchSets[sFirstChar].push(row);
         }
-    
-        // loop through the array and create a lookup structure
-        for( var i=0; i < options.data.length; i++ ){
-          // if row is a string, make an array otherwise just reference the array
-          row = ((typeof options.data[i] == "string") ? [options.data[i]] : options.data[i]);
-    
-          // if the length is zero, don't add to list
-          if( row[0].length > 0 ){
-            // get the first character
-            sFirstChar = row[0].substring(0, 1).toLowerCase();
-            // if no lookup array for this character exists, look it up now
-            if( !stMatchSets[sFirstChar] ) stMatchSets[sFirstChar] = [];
-            // if the match is a string
-            stMatchSets[sFirstChar].push(row);
-          }
-        }
-    
-        // add the data items to the cache
-        if ( options.cacheLength )
-        {
-          for( var k in stMatchSets ) {
-            // increase the cache size
-            options.cacheLength++;
-            // add to the cache
-            addToCache(k, stMatchSets[k]);
-          }
+      }
+  
+      // add the data items to the cache
+      if ( options.cacheLength )
+      {
+        for( var k in stMatchSets ) {
+          // increase the cache size
+          options.cacheLength++;
+          // add to the cache
+          addToCache(k, stMatchSets[k]);
         }
       }
-    
-      $input
-      .keydown(function(e) {
-        // track last key pressed
-        lastKeyPressCode = e.keyCode;
-        switch(e.keyCode) {
-          case 38: // up
-            e.preventDefault();
-            moveSelect(-1);
-            break;
-          case 40: // down
+    }
+  
+    $input
+    .keydown(function(e) {
+      // track last key pressed
+      lastKeyPressCode = e.keyCode;
+      switch(e.keyCode) {
+        case 38: // up
+          e.preventDefault();
+          moveSelect(-1);
+          break;
+        case 40: // down
+          e.preventDefault();
+          moveSelect(1);
+          break;
+        case 9:  // tab
+        case 13: // return
+          if( selectCurrent() ){
+            // make sure to blur off the current field
+            // (Enano edit - why do we want this, again?)
+            // $input.get(0).blur();
             e.preventDefault();
-            moveSelect(1);
-            break;
-          case 9:  // tab
-          case 13: // return
-            if( selectCurrent() ){
-              // make sure to blur off the current field
-              // (Enano edit - why do we want this, again?)
-              // $input.get(0).blur();
-              e.preventDefault();
-            }
-            break;
-          default:
-            active = -1;
-            if (timeout) clearTimeout(timeout);
-            timeout = setTimeout(function(){onChange();}, options.delay);
-            break;
-        }
-      })
-      .focus(function(){
-        // track whether the field has focus, we shouldn't process any results if the field no longer has focus
-        hasFocus = true;
-      })
-      .blur(function() {
-        // track whether the field has focus
-        hasFocus = false;
-        if (!mouseDownOnSelect) {
-          hideResults();
-        }
-      });
-    
-      hideResultsNow();
-    
-      function onChange() {
-        // ignore if the following keys are pressed: [del] [shift] [capslock]
-        if( lastKeyPressCode == 46 || (lastKeyPressCode > 8 && lastKeyPressCode < 32) ) return $results.hide();
-        var v = $input.val();
-        if (v == prev) return;
-        prev = v;
-        if (v.length >= options.minChars) {
-          $input.addClass(options.loadingClass);
-          requestData(v);
-        } else {
-          $input.removeClass(options.loadingClass);
-          $results.hide();
+          }
+          break;
+        default:
+          active = -1;
+          if (timeout) clearTimeout(timeout);
+          timeout = setTimeout(function(){onChange();}, options.delay);
+          break;
+      }
+    })
+    .focus(function(){
+      // track whether the field has focus, we shouldn't process any results if the field no longer has focus
+      hasFocus = true;
+    })
+    .blur(function() {
+      // track whether the field has focus
+      hasFocus = false;
+      if (!mouseDownOnSelect) {
+        hideResults();
+      }
+    });
+  
+    hideResultsNow();
+  
+    function onChange() {
+      // ignore if the following keys are pressed: [del] [shift] [capslock]
+      if( lastKeyPressCode == 46 || (lastKeyPressCode > 8 && lastKeyPressCode < 32) ) return $results.hide();
+      var v = $input.val();
+      if (v == prev) return;
+      prev = v;
+      if (v.length >= options.minChars) {
+        $input.addClass(options.loadingClass);
+        requestData(v);
+      } else {
+        $input.removeClass(options.loadingClass);
+        $results.hide();
+      }
+    };
+  
+    function moveSelect(step) {
+  
+      var lis = $("td", results);
+      if (!lis || hasNoResults) return;
+  
+      active += step;
+  
+      if (active < 0) {
+        active = 0;
+      } else if (active >= lis.size()) {
+        active = lis.size() - 1;
+      }
+  
+      lis.removeClass("row2");
+  
+      $(lis[active]).addClass("row2");
+      
+      // scroll the results div
+      // are we going up or down?
+      var td_top = $dynano(lis[active]).Top() - $dynano(results).Top();
+      var td_height = $dynano(lis[active]).Height();
+      var td_bottom = td_top + td_height;
+      var visibleTopBoundary = getScrollOffset(results);
+      var results_height = $dynano(results).Height();
+      var visibleBottomBoundary = visibleTopBoundary + results_height;
+      var scrollTo = false;
+      if ( td_top < visibleTopBoundary && step < 0 )
+      {
+        // going up: scroll the results div to just higher than the result we're trying to see
+        scrollTo = td_top - 7;
+      }
+      else if ( td_bottom > visibleBottomBoundary && step > 0 )
+      {
+        // going down is a little harder, we want the result to be at the bottom
+        scrollTo = td_top - results_height + td_height + 7;
+      }
+      if ( scrollTo )
+      {
+        results.scrollTop = scrollTo;
+      }
+  
+      // Weird behaviour in IE
+      // if (lis[active] && lis[active].scrollIntoView) {
+      // 	lis[active].scrollIntoView(false);
+      // }
+  
+    };
+  
+    function selectCurrent() {
+      var li = $("td.row2", results)[0];
+      if (!li) {
+        var $li = $("td", results);
+        if (options.selectOnly) {
+          if ($li.length == 1) li = $li[0];
+        } else if (options.selectFirst) {
+          li = $li[0];
         }
-      };
-    
-      function moveSelect(step) {
-    
-        var lis = $("td", results);
-        if (!lis || hasNoResults) return;
-    
-        active += step;
-    
-        if (active < 0) {
-          active = 0;
-        } else if (active >= lis.size()) {
-          active = lis.size() - 1;
+      }
+      if (li) {
+        selectItem(li);
+        return true;
+      } else {
+        return false;
+      }
+    };
+  
+    function selectItem(li) {
+      if (!li) {
+        li = document.createElement("li");
+        li.extra = [];
+        li.selectValue = "";
+      }
+      var v = $.trim(li.selectValue ? li.selectValue : li.innerHTML);
+      input.lastSelected = v;
+      prev = v;
+      $results.html("");
+      $input.val(v);
+      hideResultsNow();
+      if (options.onItemSelect) {
+        setTimeout(function() { options.onItemSelect(li) }, 1);
+      }
+    };
+  
+    // selects a portion of the input string
+    function createSelection(start, end){
+      // get a reference to the input element
+      var field = $input.get(0);
+      if( field.createTextRange ){
+        var selRange = field.createTextRange();
+        selRange.collapse(true);
+        selRange.moveStart("character", start);
+        selRange.moveEnd("character", end);
+        selRange.select();
+      } else if( field.setSelectionRange ){
+        field.setSelectionRange(start, end);
+      } else {
+        if( field.selectionStart ){
+          field.selectionStart = start;
+          field.selectionEnd = end;
         }
+      }
+      field.focus();
+    };
+  
+    // fills in the input box w/the first match (assumed to be the best match)
+    function autoFill(sValue){
+      // if the last user key pressed was backspace, don't autofill
+      if( lastKeyPressCode != 8 ){
+        // fill in the value (keep the case the user has typed)
+        $input.val($input.val() + sValue.substring(prev.length));
+        // select the portion of the value not typed by the user (so the next character will erase)
+        createSelection(prev.length, sValue.length);
+      }
+    };
+  
+    function showResults() {
+      // get the position of the input field right now (in case the DOM is shifted)
+      var pos = findPos(input);
+      // either use the specified width, or autocalculate based on form element
+      var iWidth = (options.width > 0) ? options.width : $input.width();
+      // reposition
+      $results.css({
+        width: parseInt(iWidth) + "px",
+        top: (pos.y + input.offsetHeight) + "px",
+        left: pos.x + "px"
+      });
+      if ( !$results.is(":visible") )
+      {
+        $results.show("blind", {}, 200);
+      }
+      else
+      {
+        $results.show();
+      }
+    };
+  
+    function hideResults() {
+      if (timeout) clearTimeout(timeout);
+      timeout = setTimeout(hideResultsNow, 200);
+    };
+  
+    function hideResultsNow() {
+      if (hidingResults) {
+        return;
+      }
+      hidingResults = true;
     
-        lis.removeClass("row2");
-    
-        $(lis[active]).addClass("row2");
-        
-        // scroll the results div
-        // are we going up or down?
-        var td_top = $dynano(lis[active]).Top() - $dynano(results).Top();
-        var td_height = $dynano(lis[active]).Height();
-        var td_bottom = td_top + td_height;
-        var visibleTopBoundary = getScrollOffset(results);
-        var results_height = $dynano(results).Height();
-        var visibleBottomBoundary = visibleTopBoundary + results_height;
-        var scrollTo = false;
-        if ( td_top < visibleTopBoundary && step < 0 )
+      if (timeout) {
+        clearTimeout(timeout);
+      }
+      
+      var v = $input.removeClass(options.loadingClass).val();
+      
+      if ($results.is(":visible")) {
+        $results.hide();
+      }
+      
+      if (options.mustMatch) {
+        if (!input.lastSelected || input.lastSelected != v) {
+          selectItem(null);
+        }
+      }
+  
+      hidingResults = false;
+    };
+  
+    function receiveData(q, data) {
+      if (data) {
+        $input.removeClass(options.loadingClass);
+        results.innerHTML = "";
+  
+        // if the field no longer has focus or if there are no matches, do not display the drop down
+        if( !hasFocus )
         {
-          // going up: scroll the results div to just higher than the result we're trying to see
-          scrollTo = td_top - 7;
+          return hideResultsNow();
         }
-        else if ( td_bottom > visibleBottomBoundary && step > 0 )
+        if ( data.length == 0 && !options.showWhenNoResults )
         {
-          // going down is a little harder, we want the result to be at the bottom
-          scrollTo = td_top - results_height + td_height + 7;
+          return hideResultsNow();
         }
-        if ( scrollTo )
-        {
-          console.debug('scrolling the results div to %d', scrollTo);
-          results.scrollTop = scrollTo;
+        hasNoResults = false;
+  
+        if ($.browser.msie) {
+          // we put a styled iframe behind the calendar so HTML SELECT elements don't show through
+          $results.append(document.createElement('iframe'));
         }
-    
-        // Weird behaviour in IE
-        // if (lis[active] && lis[active].scrollIntoView) {
-        // 	lis[active].scrollIntoView(false);
-        // }
-    
-      };
-    
-      function selectCurrent() {
-        var li = $("td.row2", results)[0];
-        if (!li) {
-          var $li = $("td", results);
-          if (options.selectOnly) {
-            if ($li.length == 1) li = $li[0];
-          } else if (options.selectFirst) {
-            li = $li[0];
+        results.appendChild(dataToDom(data));
+        // autofill in the complete box w/the first match as long as the user hasn't entered in more data
+        if( options.autoFill && ($input.val().toLowerCase() == q.toLowerCase()) ) autoFill(data[0][0]);
+        showResults();
+      } else {
+        hideResultsNow();
+      }
+    };
+  
+    function parseData(data) {
+      if (!data) return null;
+      var parsed = parseJSON(data);
+      return parsed;
+    };
+  
+    function dataToDom(data) {
+      var ul = document.createElement("table");
+      $(ul).attr("border", "0").attr("cellspacing", "1").attr("cellpadding", "3");
+      var num = data.length;
+      
+      if ( options.tableHeader )
+      {
+        ul.innerHTML = options.tableHeader;
+      }
+      
+      if ( num == 0 )
+      {
+        // not showing any results
+        if ( options.noResultsHTML )
+          ul.innerHTML += options.noResultsHTML;
+        
+        hasNoResults = true;
+        return ul;
+      }
+      
+      // limited results to a max number
+      if( (options.maxItemsToShow > 0) && (options.maxItemsToShow < num) ) num = options.maxItemsToShow;
+      
+      for (var i=0; i < num; i++) {
+        var row = data[i];
+        if (!row) continue;
+        
+        if ( typeof(row[0]) != 'string' )
+        {
+          // last ditch resort if it's a 1.1.4 autocomplete plugin that doesn't provide an automatic result.
+          // hopefully this doesn't slow it down a lot.
+          for ( var i in row )
+          {
+            if ( i == "0" || i == 0 )
+              break;
+            row[0] = row[i];
+            break;
           }
         }
-        if (li) {
-          selectItem(li);
-          return true;
-        } else {
-          return false;
-        }
-      };
-    
-      function selectItem(li) {
-        if (!li) {
-          li = document.createElement("li");
-          li.extra = [];
-          li.selectValue = "";
-        }
-        var v = $.trim(li.selectValue ? li.selectValue : li.innerHTML);
-        input.lastSelected = v;
-        prev = v;
-        $results.html("");
-        $input.val(v);
-        hideResultsNow();
-        if (options.onItemSelect) {
-          setTimeout(function() { options.onItemSelect(li) }, 1);
-        }
-      };
-    
-      // selects a portion of the input string
-      function createSelection(start, end){
-        // get a reference to the input element
-        var field = $input.get(0);
-        if( field.createTextRange ){
-          var selRange = field.createTextRange();
-          selRange.collapse(true);
-          selRange.moveStart("character", start);
-          selRange.moveEnd("character", end);
-          selRange.select();
-        } else if( field.setSelectionRange ){
-          field.setSelectionRange(start, end);
-        } else {
-          if( field.selectionStart ){
-            field.selectionStart = start;
-            field.selectionEnd = end;
-          }
-        }
-        field.focus();
-      };
-    
-      // fills in the input box w/the first match (assumed to be the best match)
-      function autoFill(sValue){
-        // if the last user key pressed was backspace, don't autofill
-        if( lastKeyPressCode != 8 ){
-          // fill in the value (keep the case the user has typed)
-          $input.val($input.val() + sValue.substring(prev.length));
-          // select the portion of the value not typed by the user (so the next character will erase)
-          createSelection(prev.length, sValue.length);
-        }
-      };
-    
-      function showResults() {
-        // get the position of the input field right now (in case the DOM is shifted)
-        var pos = findPos(input);
-        // either use the specified width, or autocalculate based on form element
-        var iWidth = (options.width > 0) ? options.width : $input.width();
-        // reposition
-        $results.css({
-          width: parseInt(iWidth) + "px",
-          top: (pos.y + input.offsetHeight) + "px",
-          left: pos.x + "px"
-        });
-        if ( !$results.is(":visible") )
+        
+        var li = document.createElement("tr");
+        var td = document.createElement("td");
+        td.selectValue = row[0];
+        $(td).addClass('row1');
+        $(td).css("font-size", "smaller");
+        
+        if ( options.formatItem )
         {
-          $results.show("blind", {}, 200);
+          td.innerHTML = options.formatItem(row, i, num);
         }
         else
         {
-          $results.show();
-        }
-      };
-    
-      function hideResults() {
-        if (timeout) clearTimeout(timeout);
-        timeout = setTimeout(hideResultsNow, 200);
-      };
-    
-      function hideResultsNow() {
-        if (hidingResults) {
-          return;
+          td.innerHTML = row[0];
         }
-        hidingResults = true;
-      
-        if (timeout) {
-          clearTimeout(timeout);
-        }
-        
-        var v = $input.removeClass(options.loadingClass).val();
+        li.appendChild(td);
+        ul.appendChild(li);
         
-        if ($results.is(":visible")) {
-          $results.hide();
-        }
-        
-        if (options.mustMatch) {
-          if (!input.lastSelected || input.lastSelected != v) {
-            selectItem(null);
+        $(td).hover(
+          function() { $("tr", ul).removeClass("row2"); $(this).addClass("row2"); active = $("tr", ul).indexOf($(this).get(0)); },
+          function() { $(this).removeClass("row2"); }
+        ).click(function(e) { 
+          e.preventDefault();
+          e.stopPropagation();
+          selectItem(this)
+        });
+      }
+      
+      $(ul).mousedown(function() {
+        mouseDownOnSelect = true;
+      }).mouseup(function() {
+        mouseDownOnSelect = false;
+      });
+      return ul;
+    };
+  
+    function requestData(q) {
+      if (!options.matchCase) q = q.toLowerCase();
+      var data = options.cacheLength ? loadFromCache(q) : null;
+      // recieve the cached data
+      if (data) {
+        receiveData(q, data);
+      // if an AJAX url has been supplied, try loading the data now
+      } else if( (typeof options.url == "string") && (options.url.length > 0) ){
+        $.get(makeUrl(q), function(data) {
+          data = parseData(data);
+          addToCache(q, data);
+          receiveData(q, data);
+        });
+      // if there's been no data found, remove the loading class
+      } else {
+        $input.removeClass(options.loadingClass);
+      }
+    };
+  
+    function makeUrl(q) {
+      var sep = options.url.indexOf('?') == -1 ? '?' : '&'; 
+      var url = options.url + encodeURI(q);
+      for (var i in options.extraParams) {
+        url += "&" + i + "=" + encodeURI(options.extraParams[i]);
+      }
+      return url;
+    };
+  
+    function loadFromCache(q) {
+      if (!q) return null;
+      if (cache.data[q]) return cache.data[q];
+      if (options.matchSubset) {
+        for (var i = q.length - 1; i >= options.minChars; i--) {
+          var qs = q.substr(0, i);
+          var c = cache.data[qs];
+          if (c) {
+            var csub = [];
+            for (var j = 0; j < c.length; j++) {
+              var x = c[j];
+              var x0 = x[0];
+              if (matchSubset(x0, q)) {
+                csub[csub.length] = x;
+              }
+            }
+            return csub;
           }
         }
-    
-        hidingResults = false;
-      };
-    
-      function receiveData(q, data) {
-        if (data) {
-          $input.removeClass(options.loadingClass);
-          results.innerHTML = "";
-    
-          // if the field no longer has focus or if there are no matches, do not display the drop down
-          if( !hasFocus )
-          {
-            return hideResultsNow();
-          }
-          if ( data.length == 0 && !options.showWhenNoResults )
-          {
-            return hideResultsNow();
-          }
-          hasNoResults = false;
-    
-          if ($.browser.msie) {
-            // we put a styled iframe behind the calendar so HTML SELECT elements don't show through
-            $results.append(document.createElement('iframe'));
+      }
+      return null;
+    };
+  
+    function matchSubset(s, sub) {
+      if (!options.matchCase) s = s.toLowerCase();
+      var i = s.indexOf(sub);
+      if (i == -1) return false;
+      return i == 0 || options.matchContains;
+    };
+  
+    this.flushCache = function() {
+      flushCache();
+    };
+  
+    this.setExtraParams = function(p) {
+      options.extraParams = p;
+    };
+  
+    this.findValue = function(){
+      var q = $input.val();
+  
+      if (!options.matchCase) q = q.toLowerCase();
+      var data = options.cacheLength ? loadFromCache(q) : null;
+      if (data) {
+        findValueCallback(q, data);
+      } else if( (typeof options.url == "string") && (options.url.length > 0) ){
+        $.get(makeUrl(q), function(data) {
+          data = parseData(data)
+          addToCache(q, data);
+          findValueCallback(q, data);
+        });
+      } else {
+        // no matches
+        findValueCallback(q, null);
+      }
+    }
+  
+    function findValueCallback(q, data){
+      if (data) $input.removeClass(options.loadingClass);
+  
+      var num = (data) ? data.length : 0;
+      var li = null;
+  
+      for (var i=0; i < num; i++) {
+        var row = data[i];
+  
+        if( row[0].toLowerCase() == q.toLowerCase() ){
+          li = document.createElement("li");
+          if (options.formatItem) {
+            li.innerHTML = options.formatItem(row, i, num);
+            li.selectValue = row[0];
+          } else {
+            li.innerHTML = row[0];
+            li.selectValue = row[0];
           }
-          results.appendChild(dataToDom(data));
-          // autofill in the complete box w/the first match as long as the user hasn't entered in more data
-          if( options.autoFill && ($input.val().toLowerCase() == q.toLowerCase()) ) autoFill(data[0][0]);
-          showResults();
-        } else {
-          hideResultsNow();
-        }
-      };
-    
-      function parseData(data) {
-        if (!data) return null;
-        var parsed = parseJSON(data);
-        return parsed;
-      };
-    
-      function dataToDom(data) {
-        var ul = document.createElement("table");
-        $(ul).attr("border", "0").attr("cellspacing", "1").attr("cellpadding", "3");
-        var num = data.length;
-        
-        if ( options.tableHeader )
-        {
-          ul.innerHTML = options.tableHeader;
-        }
-        
-        if ( num == 0 )
-        {
-          // not showing any results
-          if ( options.noResultsHTML )
-            ul.innerHTML += options.noResultsHTML;
-          
-          hasNoResults = true;
-          return ul;
-        }
-        
-        // limited results to a max number
-        if( (options.maxItemsToShow > 0) && (options.maxItemsToShow < num) ) num = options.maxItemsToShow;
-        
-        for (var i=0; i < num; i++) {
-          var row = data[i];
-          if (!row) continue;
-          
-          if ( typeof(row[0]) != 'string' )
-          {
-            // last ditch resort if it's a 1.1.4 autocomplete plugin that doesn't provide an automatic result.
-            // hopefully this doesn't slow it down a lot.
-            for ( var i in row )
-            {
-              if ( i == "0" || i == 0 )
-                break;
-              row[0] = row[i];
-              break;
+          var extra = null;
+          if( row.length > 1 ){
+            extra = [];
+            for (var j=1; j < row.length; j++) {
+              extra[extra.length] = row[j];
             }
           }
-          
-          var li = document.createElement("tr");
-          var td = document.createElement("td");
-          td.selectValue = row[0];
-          $(td).addClass('row1');
-          $(td).css("font-size", "smaller");
-          
-          if ( options.formatItem )
-          {
-            td.innerHTML = options.formatItem(row, i, num);
-          }
-          else
-          {
-            td.innerHTML = row[0];
-          }
-          li.appendChild(td);
-          ul.appendChild(li);
-          
-          $(td).hover(
-            function() { $("tr", ul).removeClass("row2"); $(this).addClass("row2"); active = $("tr", ul).indexOf($(this).get(0)); },
-            function() { $(this).removeClass("row2"); }
-          ).click(function(e) { 
-            e.preventDefault();
-            e.stopPropagation();
-            selectItem(this)
-          });
-        }
-        
-        $(ul).mousedown(function() {
-          mouseDownOnSelect = true;
-        }).mouseup(function() {
-          mouseDownOnSelect = false;
-        });
-        return ul;
-      };
-    
-      function requestData(q) {
-        if (!options.matchCase) q = q.toLowerCase();
-        var data = options.cacheLength ? loadFromCache(q) : null;
-        // recieve the cached data
-        if (data) {
-          receiveData(q, data);
-        // if an AJAX url has been supplied, try loading the data now
-        } else if( (typeof options.url == "string") && (options.url.length > 0) ){
-          $.get(makeUrl(q), function(data) {
-            data = parseData(data);
-            addToCache(q, data);
-            receiveData(q, data);
-          });
-        // if there's been no data found, remove the loading class
-        } else {
-          $input.removeClass(options.loadingClass);
-        }
-      };
-    
-      function makeUrl(q) {
-        var sep = options.url.indexOf('?') == -1 ? '?' : '&'; 
-        var url = options.url + encodeURI(q);
-        for (var i in options.extraParams) {
-          url += "&" + i + "=" + encodeURI(options.extraParams[i]);
-        }
-        return url;
-      };
-    
-      function loadFromCache(q) {
-        if (!q) return null;
-        if (cache.data[q]) return cache.data[q];
-        if (options.matchSubset) {
-          for (var i = q.length - 1; i >= options.minChars; i--) {
-            var qs = q.substr(0, i);
-            var c = cache.data[qs];
-            if (c) {
-              var csub = [];
-              for (var j = 0; j < c.length; j++) {
-                var x = c[j];
-                var x0 = x[0];
-                if (matchSubset(x0, q)) {
-                  csub[csub.length] = x;
-                }
-              }
-              return csub;
-            }
-          }
-        }
-        return null;
-      };
-    
-      function matchSubset(s, sub) {
-        if (!options.matchCase) s = s.toLowerCase();
-        var i = s.indexOf(sub);
-        if (i == -1) return false;
-        return i == 0 || options.matchContains;
-      };
-    
-      this.flushCache = function() {
-        flushCache();
-      };
-    
-      this.setExtraParams = function(p) {
-        options.extraParams = p;
-      };
-    
-      this.findValue = function(){
-        var q = $input.val();
-    
-        if (!options.matchCase) q = q.toLowerCase();
-        var data = options.cacheLength ? loadFromCache(q) : null;
-        if (data) {
-          findValueCallback(q, data);
-        } else if( (typeof options.url == "string") && (options.url.length > 0) ){
-          $.get(makeUrl(q), function(data) {
-            data = parseData(data)
-            addToCache(q, data);
-            findValueCallback(q, data);
-          });
-        } else {
-          // no matches
-          findValueCallback(q, null);
+          li.extra = extra;
         }
       }
-    
-      function findValueCallback(q, data){
-        if (data) $input.removeClass(options.loadingClass);
-    
-        var num = (data) ? data.length : 0;
-        var li = null;
-    
-        for (var i=0; i < num; i++) {
-          var row = data[i];
-    
-          if( row[0].toLowerCase() == q.toLowerCase() ){
-            li = document.createElement("li");
-            if (options.formatItem) {
-              li.innerHTML = options.formatItem(row, i, num);
-              li.selectValue = row[0];
-            } else {
-              li.innerHTML = row[0];
-              li.selectValue = row[0];
-            }
-            var extra = null;
-            if( row.length > 1 ){
-              extra = [];
-              for (var j=1; j < row.length; j++) {
-                extra[extra.length] = row[j];
-              }
-            }
-            li.extra = extra;
-          }
-        }
-    
-        if( options.onFindValue ) setTimeout(function() { options.onFindValue(li) }, 1);
+  
+      if( options.onFindValue ) setTimeout(function() { options.onFindValue(li) }, 1);
+    }
+  
+    function addToCache(q, data) {
+      if (!data || !q || !options.cacheLength) return;
+      if (!cache.length || cache.length > options.cacheLength) {
+        flushCache();
+        cache.length++;
+      } else if (!cache[q]) {
+        cache.length++;
       }
-    
-      function addToCache(q, data) {
-        if (!data || !q || !options.cacheLength) return;
-        if (!cache.length || cache.length > options.cacheLength) {
-          flushCache();
-          cache.length++;
-        } else if (!cache[q]) {
-          cache.length++;
-        }
-        cache.data[q] = data;
-      };
-    
-      function findPos(obj) {
-        var curleft = obj.offsetLeft || 0;
-        var curtop = obj.offsetTop || 0;
-        while (obj = obj.offsetParent) {
-          curleft += obj.offsetLeft
-          curtop += obj.offsetTop
-        }
-        return {x:curleft,y:curtop};
+      cache.data[q] = data;
+    };
+  
+    function findPos(obj) {
+      var curleft = obj.offsetLeft || 0;
+      var curtop = obj.offsetTop || 0;
+      while (obj = obj.offsetParent) {
+        curleft += obj.offsetLeft
+        curtop += obj.offsetTop
       }
+      return {x:curleft,y:curtop};
     }
-    
-    jQuery.fn.autocomplete = function(url, options, data) {
-      // Make sure options exists
-      options = options || {};
-      // Set url as option
-      options.url = url;
-      // set some bulk local data
-      options.data = ((typeof data == "object") && (data.constructor == Array)) ? data : null;
-    
-      // Set default values for required options
-      options = $.extend({
-        inputClass: "ac_input",
-        resultsClass: "ac_results",
-        lineSeparator: "\n",
-        cellSeparator: "|",
-        minChars: 1,
-        delay: 400,
-        matchCase: 0,
-        matchSubset: 1,
-        matchContains: 0,
-        cacheLength: false,
-        mustMatch: 0,
-        extraParams: {},
-        loadingClass: "ac_loading",
-        selectFirst: false,
-        selectOnly: false,
-        maxItemsToShow: -1,
-        autoFill: false,
-        showWhenNoResults: false,
-        width: 0
-      }, options);
-      options.width = parseInt(options.width, 10);
-    
-      this.each(function() {
-        var input = this;
-        new jQuery.autocomplete(input, options);
-      });
-    
-      // Don't break the chain
-      return this;
+  }
+  
+  jQuery.fn.autocomplete = function(url, options, data) {
+    // Make sure options exists
+    options = options || {};
+    // Set url as option
+    options.url = url;
+    // set some bulk local data
+    options.data = ((typeof data == "object") && (data.constructor == Array)) ? data : null;
+  
+    // Set default values for required options
+    options = $.extend({
+      inputClass: "ac_input",
+      resultsClass: "ac_results",
+      lineSeparator: "\n",
+      cellSeparator: "|",
+      minChars: 1,
+      delay: 400,
+      matchCase: 0,
+      matchSubset: 1,
+      matchContains: 0,
+      cacheLength: false,
+      mustMatch: 0,
+      extraParams: {},
+      loadingClass: "ac_loading",
+      selectFirst: false,
+      selectOnly: false,
+      maxItemsToShow: -1,
+      autoFill: false,
+      showWhenNoResults: false,
+      width: 0
+    }, options);
+    options.width = parseInt(options.width, 10);
+  
+    this.each(function() {
+      var input = this;
+      new jQuery.autocomplete(input, options);
+    });
+  
+    // Don't break the chain
+    return this;
+  }
+  
+  jQuery.fn.autocompleteArray = function(data, options) {
+    return this.autocomplete(null, options, data);
+  }
+  
+  jQuery.fn.indexOf = function(e){
+    for( var i=0; i<this.length; i++ ){
+      if( this[i] == e ) return i;
     }
-    
-    jQuery.fn.autocompleteArray = function(data, options) {
-      return this.autocomplete(null, options, data);
-    }
-    
-    jQuery.fn.indexOf = function(e){
-      for( var i=0; i<this.length; i++ ){
-        if( this[i] == e ) return i;
-      }
-      return -1;
-    };
-    
-    autofill_onload();
-  });
+    return -1;
+  };
+  
+  autofill_onload();
+};
+
+addOnloadHook(autofill_init);