includes/clientside/static/autofill.js
changeset 699 c7d737202d59
parent 687 ea43ac1ff2ee
child 701 dd80cde96a6c
--- a/includes/clientside/static/autofill.js	Sun Aug 17 23:24:41 2008 -0400
+++ b/includes/clientside/static/autofill.js	Thu Aug 21 08:24:04 2008 -0400
@@ -1,55 +1,66 @@
 /**
- * Javascript auto-completion for form fields. This supercedes the code in autocomplete.js for MOZILLA ONLY. It doesn't seem to work real
- * well with other browsers yet.
+ * Javascript auto-completion for form fields. jQuery based in 1.1.5.
+ * Different types of auto-completion fields can be defined (e.g. with different data sets). For each one, a schema
+ * can be created describing how to draw each row.
  */
 
-// fill schemas
 var autofill_schemas = {};
 
-// default, generic schema
+/**
+ * SCHEMA - GENERIC
+ */
+
 autofill_schemas.generic = {
-  template: '<div id="--ID--_region" spry:region="autofill_ds_--CLASS--" class="tblholder">' + "\n" +
-            '  <table border="0" cellspacing="1" cellpadding="3" style="font-size: smaller;">' + "\n" +
-            '    <tr spry:repeat="autofill_region_--ID--">' + "\n" +
-            '      <td class="row1" spry:suggest="{name}">{name}</td>' + "\n" +
-            '    </tr>' + "\n" +
-            '  </table>' + "\n" +
-            '</div>',
-  
-  init: function(element, fillclass)
+  init: function(element, fillclass, params)
+  {
+    $(element).autocomplete(makeUrlNS('Special', 'Autofill', 'type=' + fillclass) + '&userinput=', {
+        minChars: 3
+    });
+  }
+}
+
+autofill_schemas.username = {
+  init: function(element, fillclass, params)
   {
-    // calculate positions before spry f***s everything up
-    var top = $dynano(element).Top() + $dynano(element).Height() - 10; // tblholder has 10px top margin
-    var left = $dynano(element).Left();
-    
-    // dataset name
-    var ds_name = 'autofill_ds_' + fillclass;
-    
-    // setup the dataset
-    window[ds_name] = new Spry.Data.JSONDataSet(makeUrlNS('Special', 'Autofill', 'type=' + fillclass));
-    
-    // inject our HTML wrapper
-    var template = this.template.replace(new RegExp('--ID--', 'g'), element.id).replace(new RegExp('--CLASS--', 'g', fillclass));
-    var wrapper = element.parentNode; // document.createElement('div');
-    if ( !wrapper.id )
-      wrapper.id = 'autofill_wrap_' + element.id;
-    
-    // a bunch of hacks to add a spry wrapper
-    wrapper.innerHTML = template + wrapper.innerHTML;
-    
-    var autosuggest = new Spry.Widget.AutoSuggest(wrapper.id, element.id + '_region', window[ds_name], 'name', {loadFromServer: true, urlParam: 'userinput', hoverSuggestClass: 'row2', minCharsType: 3});
-    var regiondiv = document.getElementById(element.id + '_region');
-    regiondiv.style.position = 'absolute';
-    regiondiv.style.top = top + 'px';
-    regiondiv.style.left = left + 'px';
+    $(element).autocomplete(makeUrlNS('Special', 'Autofill', 'type=' + fillclass) + '&userinput=', {
+        minChars: 3,
+        formatItem: function(row, _, __)
+        {
+          var html = row.name_highlight + '<br />';
+          html += '<span style="' + row.rank_style + '">' + row.rank_title + '</span>';
+          return html;
+        },
+        tableHeader: '<tr><th>' + $lang.get('user_autofill_heading_suggestions') + '</th></tr>',
+    });
   }
-};
+}
+
+window.autofill_onload = function()
+{
+  if ( this.loaded )
+  {
+    return true;
+  }
+  
+  var inputs = document.getElementsByClassName('input', 'autofill');
+  
+  if ( inputs.length > 0 )
+  {
+    // we have at least one input that needs to be made an autofill element.
+    // is spry data loaded?
+    load_component('template-compiler');
+  }
+  
+  this.loaded = true;
+  
+  for ( var i = 0; i < inputs.length; i++ )
+  {
+    autofill_init_element(inputs[i]);
+  }
+}
 
 function autofill_init_element(element, params)
 {
-  if ( !Spry.Data );
-    load_spry_data();
-  
   params = params || {};
   // assign an ID if it doesn't have one yet
   if ( !element.id )
@@ -73,115 +84,617 @@
   element.af_initted = true;
 }
 
-var autofill_onload = function()
+function AutofillUsername(el, allow_anon)
 {
-  if ( this.loaded )
-  {
-    return true;
-  }
-  
-  autofill_schemas.username = {
-    template: '<div id="--ID--_region" spry:region="autofill_ds_username" class="tblholder">' + "\n" +
-              '  <table border="0" cellspacing="1" cellpadding="3" style="font-size: smaller;">' + "\n" +
-              '    <tr>' + "\n" +
-              '      <th>' + $lang.get('user_autofill_heading_suggestions') + '</th>' + "\n" +
-              '    </tr>' + "\n" +
-              '    <tr spry:repeat="autofill_region_--ID--">' + "\n" +
-              '      <td class="row1" spry:suggest="{name}">{name_highlight}<br /><small style="{rank_style}">{rank_title}</small></td>' + "\n" +
-              '    </tr>' + "\n" +
-              '  </table>' + "\n" +
-              '</div>',
-    
-    init: function(element, fillclass, params)
-    {
-      // calculate positions before spry f***s everything up
-      var top = $dynano(element).Top() + $dynano(element).Height() - 10; // tblholder has 10px top margin
-      var left = $dynano(element).Left();
-      
-      var allow_anon = ( params.allow_anon ) ? '1' : '0';
-      // setup the dataset
-      if ( !window.autofill_ds_username )
-      {
-        window.autofill_ds_username = new Spry.Data.JSONDataSet(makeUrlNS('Special', 'Autofill', 'type=' + fillclass + '&allow_anon' + allow_anon));
-      }
-      
-      // inject our HTML wrapper
-      var template = this.template.replace(new RegExp('--ID--', 'g'), element.id);
-      var wrapper = element.parentNode; // document.createElement('div');
-      if ( !wrapper.id )
-        wrapper.id = 'autofill_wrap_' + element.id;
-      
-      // a bunch of hacks to add a spry wrapper
-      wrapper.innerHTML = template + wrapper.innerHTML;
-      
-      var autosuggest = new Spry.Widget.AutoSuggest(wrapper.id, element.id + '_region', window.autofill_ds_username, 'name', {loadFromServer: true, urlParam: 'userinput', hoverSuggestClass: 'row2', minCharsType: 3});
-      var regiondiv = document.getElementById(element.id + '_region');
-      regiondiv.style.position = 'absolute';
-      regiondiv.style.top = top + 'px';
-      regiondiv.style.left = left + 'px';
-    }
-  };
-  
-  autofill_schemas.page = {
-    template: '<div id="--ID--_region" spry:region="autofill_region_--ID--" class="tblholder">' + "\n" +
-              '  <table border="0" cellspacing="1" cellpadding="3" style="font-size: smaller;">' + "\n" +
-              '    <tr>' + "\n" +
-              '      <th colspan="2">' + $lang.get('page_autosuggest_heading') + '</th>' + "\n" +
-              '    </tr>' + "\n" +
-              '    <tr spry:repeat="autofill_region_--ID--">' + "\n" +
-              '      <td class="row1" spry:suggest="{page_id}">{pid_highlight}<br /><small>{name_highlight}</small></td>' + "\n" +
-              '    </tr>' + "\n" +
-              '  </table>' + "\n" +
-              '</div>'
-  }
-  
-  var inputs = document.getElementsByClassName('input', 'autofill');
-  
-  if ( inputs.length > 0 )
-  {
-    // we have at least one input that needs to be made an autofill element.
-    // is spry data loaded?
-    if ( !Spry.Data )
-    {
-      load_spry_data();
-      return true;
-    }
-  }
-  
-  this.loaded = true;
-  
-  for ( var i = 0; i < inputs.length; i++ )
-  {
-    autofill_init_element(inputs[i]);
-  }
+  el.onkeyup = null;
+  el.className = 'autofill username';
+  autofill_init_element(el, { allow_anon: allow_anon });
+}
+
+function AutofillPage(el)
+{
+  el.onkeyup = null;
+  el.className = 'autofill page';
+  autofill_init_element(el, {});
 }
 
-addOnloadHook(autofill_onload);
-
-function autofill_force_region_refresh()
-{
-  Spry.Data.initRegions();
-}
-
-function AutofillUsername(element, event, allowanon)
-{
-  element.onkeyup = element.onkeydown = element.onkeypress = function(e) {};
-  
-  element.className = 'autofill username';
-  
-  allowanon = allowanon ? true : false;
-  autofill_init_element(element, {
-      allow_anon: allowanon
-    });
-}
-
-// load spry data components
-function load_spry_data()
-{
-  var scripts = [ 'SpryData.js', 'SpryJSONDataSet.js', 'SpryAutoSuggest.js' ];
-  for ( var i = 0; i < scripts.length; i++ )
+addOnloadHook(function()
   {
-    load_component(scripts[i]);
-  }
-  autofill_onload();
-}
+    load_component('jquery');
+    load_component('jquery-ui');
+    
+    if ( !window.jQuery )
+    {
+      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);
+    
+      // 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);
+      }
+      else
+      {
+        $results.css("width", "200px");
+      }
+    
+      // Add to body element
+      $("body").append(results);
+    
+      input.autocompleter = me;
+    
+      var timeout = null;
+      var prev = "";
+      var active = -1;
+      var cache = {};
+      var keyb = false;
+      var hasFocus = 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);
+          }
+        }
+    
+        // add the data items to the cache
+        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
+            e.preventDefault();
+            moveSelect(1);
+            break;
+          case 9:  // tab
+          case 13: // return
+            if( selectCurrent() ){
+              // make sure to blur off the current field
+              $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();
+        }
+      };
+    
+      function moveSelect(step) {
+    
+        var lis = $("td", results);
+        if (!lis) return;
+    
+        active += step;
+    
+        if (active < 0) {
+          active = 0;
+        } else if (active >= lis.size()) {
+          active = lis.size() - 1;
+        }
+    
+        lis.removeClass("row2");
+    
+        $(lis[active]).addClass("row2");
+    
+        // 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];
+          }
+        }
+        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", {}, 350);
+        }
+      };
+    
+      function hideResults() {
+        if (timeout) clearTimeout(timeout);
+        timeout = setTimeout(hideResultsNow, 200);
+      };
+    
+      function hideResultsNow() {
+        if (hidingResults) {
+          return;
+        }
+        hidingResults = true;
+      
+        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 || data.length == 0 ) return hideResultsNow();
+    
+          if ($.browser.msie) {
+            // we put a styled iframe behind the calendar so HTML SELECT elements don't show through
+            $results.append(document.createElement('iframe'));
+          }
+          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;
+        }
+    
+        // 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;
+          
+          var li = document.createElement("tr");
+          var td = document.createElement("td");
+          td.selectValue = row[0];
+          $(td).addClass('row1');
+          $(td).css("font-size", "smaller");
+          console.debug(ul, li, td);
+          
+          if ( options.formatItem )
+          {
+            td.innerHTML = options.formatItem(row, i, num);
+          }
+          else
+          {
+            td.innerHTML = row[0];
+          }
+          li.appendChild(td);
+          var extra = null;
+          if (row.length > 1) {
+            extra = [];
+            for (var j=1; j < row.length; j++) {
+              extra[extra.length] = row[j];
+            }
+          }
+          td.extra = extra;
+          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)
+          });
+          
+          /*
+          var 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;
+          ul.appendChild(li);
+          
+          $(li).hover(
+            function() { $("li", ul).removeClass("ac_over"); $(this).addClass("ac_over"); active = $("li", ul).indexOf($(this).get(0)); },
+            function() { $(this).removeClass("ac_over"); }
+          ).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);
+        }
+      }
+    
+      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);
+      }
+    
+      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};
+      }
+    }
+    
+    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: 1,
+        mustMatch: 0,
+        extraParams: {},
+        loadingClass: "ac_loading",
+        selectFirst: false,
+        selectOnly: false,
+        maxItemsToShow: -1,
+        autoFill: 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;
+      }
+      return -1;
+    };
+    
+    autofill_onload();
+  });