Index: keyword_autocomplete.js
===================================================================
--- keyword_autocomplete.js (revision 0)
+++ keyword_autocomplete.js (revision 1615)
@@ -0,0 +1,310 @@
+// $Id:
+
+/**
+ * @file
+ * Variation of misc/autocomplete.js.
+ * 
+ * Altered to submit a form when an autocomplete value is selected.  This is
+ * required by some features of the Keyword Autocomplete module.
+ */
+
+/**
+ * Attaches the keyword_autocomplete behavior to all required fields
+ */
+Drupal.behaviors.keyword_autocomplete = function (context) {
+  var acdb = [];
+  $('input.keyword-autocomplete:not(.autocomplete-processed)', context).each(function () {
+    var uri = this.value;
+    if (!acdb[uri]) {
+      acdb[uri] = new Drupal.ACDB(uri);
+    }
+    var input = $('#' + this.id.substr(0, this.id.length - 13))
+      .attr('autocomplete', 'OFF')[0];
+    $(input.form).submit(Drupal.keyword_autocompleteSubmit);
+    new Drupal.jsFAC(input, acdb[uri]);
+    $(this).addClass('autocomplete-processed');
+  });
+};
+
+/**
+ * Prevents the form from submitting if the suggestions popup is open
+ * and closes the suggestions popup when doing so.
+ */
+Drupal.keyword_autocompleteSubmit = function () {
+  return $('#autocomplete').each(function () {
+    this.owner.hidePopup();
+	this.form.submit();
+  }).size() == 0;
+};
+
+/**
+ * An AutoComplete object
+ */
+Drupal.jsFAC = function (input, db) {
+  var ac = this;
+  this.input = input;
+  this.db = db;
+
+  $(this.input)
+    .keydown(function (event) { return ac.onkeydown(this, event); })
+    .keyup(function (event) { ac.onkeyup(this, event); })
+    .blur(function () { ac.hidePopup(); ac.db.cancel(); });
+
+};
+
+/**
+ * Handler for the "keydown" event
+ */
+Drupal.jsFAC.prototype.onkeydown = function (input, e) {
+  if (!e) {
+    e = window.event;
+  }
+  switch (e.keyCode) {
+    case 40: // down arrow
+      this.selectDown();
+      return false;
+    case 38: // up arrow
+      this.selectUp();
+      return false;
+    default: // all other keys
+      return true;
+  }
+};
+
+/**
+ * Handler for the "keyup" event
+ */
+Drupal.jsFAC.prototype.onkeyup = function (input, e) {
+  if (!e) {
+    e = window.event;
+  }
+  switch (e.keyCode) {
+    case 16: // shift
+    case 17: // ctrl
+    case 18: // alt
+    case 20: // caps lock
+    case 33: // page up
+    case 34: // page down
+    case 35: // end
+    case 36: // home
+    case 37: // left arrow
+    case 38: // up arrow
+    case 39: // right arrow
+    case 40: // down arrow
+      return true;
+
+    case 9:  // tab
+    case 13: // enter
+    case 27: // esc
+      this.hidePopup(e.keyCode);
+      return true;
+
+    default: // all other keys
+      if (input.value.length > 0)
+        this.populatePopup();
+      else
+        this.hidePopup(e.keyCode);
+      return true;
+  }
+};
+
+/**
+ * Puts the currently highlighted suggestion into the keyword_autocomplete field
+ */
+Drupal.jsFAC.prototype.select = function (node) {
+  this.input.value = node.keyword_autocompleteValue;
+  this.input.form.submit();
+};
+
+/**
+ * Highlights the next suggestion
+ */
+Drupal.jsFAC.prototype.selectDown = function () {
+  if (this.selected && this.selected.nextSibling) {
+    this.highlight(this.selected.nextSibling);
+  }
+  else {
+    var lis = $('li', this.popup);
+    if (lis.size() > 0) {
+      this.highlight(lis.get(0));
+    }
+  }
+};
+
+/**
+ * Highlights the previous suggestion
+ */
+Drupal.jsFAC.prototype.selectUp = function () {
+  if (this.selected && this.selected.previousSibling) {
+    this.highlight(this.selected.previousSibling);
+  }
+};
+
+/**
+ * Highlights a suggestion
+ */
+Drupal.jsFAC.prototype.highlight = function (node) {
+  if (this.selected) {
+    $(this.selected).removeClass('selected');
+  }
+  $(node).addClass('selected');
+  this.selected = node;
+};
+
+/**
+ * Unhighlights a suggestion
+ */
+Drupal.jsFAC.prototype.unhighlight = function (node) {
+  $(node).removeClass('selected');
+  this.selected = false;
+};
+
+/**
+ * Hides the keyword_autocomplete suggestions
+ */
+Drupal.jsFAC.prototype.hidePopup = function (keycode) {
+  // Select item if the right key or mousebutton was pressed
+  if (this.selected && ((keycode && keycode != 46 && keycode != 8 && keycode != 27) || !keycode)) {
+    this.input.value = this.selected.keyword_autocompleteValue;
+  }
+
+  // Hide popup
+  var popup = this.popup;
+  if (popup) {
+    this.popup = null;
+    $(popup).fadeOut('fast', function() { $(popup).remove(); });
+  }
+  this.selected = false;
+
+};
+
+/**
+ * Positions the suggestions popup and starts a search
+ */
+Drupal.jsFAC.prototype.populatePopup = function () {
+  // Show popup
+  if (this.popup) {
+    $(this.popup).remove();
+  }
+  this.selected = false;
+  this.popup = document.createElement('div');
+  this.popup.id = 'autocomplete';
+  this.popup.owner = this;
+  $(this.popup).css({
+    marginTop: this.input.offsetHeight +'px',
+    width: (this.input.offsetWidth - 4) +'px',
+    display: 'none'
+  });
+  $(this.input).before(this.popup);
+
+  // Do search
+  this.db.owner = this;
+  this.db.search(this.input.value);
+};
+
+/**
+ * Fills the suggestion popup with any matches received
+ */
+Drupal.jsFAC.prototype.found = function (matches) {
+  // If no value in the textfield, do not show the popup.
+  if (!this.input.value.length) {
+    return false;
+  }
+
+  // Prepare matches
+  var ul = document.createElement('ul');
+  var ac = this;
+  for (key in matches) {
+    var li = document.createElement('li');
+    $(li)
+      .html('<div>'+ matches[key] +'</div>')
+      .mousedown(function () { ac.select(this); })
+      .mouseover(function () { ac.highlight(this); })
+      .mouseout(function () { ac.unhighlight(this); });
+    li.keyword_autocompleteValue = key;
+    $(ul).append(li);
+  }
+
+  // Show popup with matches, if any
+  if (this.popup) {
+    if (ul.childNodes.length > 0) {
+      $(this.popup).empty().append(ul).show();
+    }
+    else {
+      $(this.popup).css({visibility: 'hidden'});
+      this.hidePopup();
+    }
+  }
+};
+
+Drupal.jsFAC.prototype.setStatus = function (status) {
+  switch (status) {
+    case 'begin':
+      $(this.input).addClass('throbbing');
+      break;
+    case 'cancel':
+    case 'error':
+    case 'found':
+      $(this.input).removeClass('throbbing');
+      break;
+  }
+};
+
+/**
+ * An AutoComplete DataBase object
+ */
+Drupal.ACDB = function (uri) {
+  this.uri = uri;
+  this.delay = 300;
+  this.cache = {};
+};
+
+/**
+ * Performs a cached and delayed search
+ */
+Drupal.ACDB.prototype.search = function (searchString) {
+  var db = this;
+  this.searchString = searchString;
+
+  // See if this key has been searched for before
+  if (this.cache[searchString]) {
+    return this.owner.found(this.cache[searchString]);
+  }
+
+  // Initiate delayed search
+  if (this.timer) {
+    clearTimeout(this.timer);
+  }
+  this.timer = setTimeout(function() {
+    db.owner.setStatus('begin');
+
+    // Ajax GET request for autocompletion
+    $.ajax({
+      type: "GET",
+      url: db.uri +'/'+ Drupal.encodeURIComponent(searchString),
+      dataType: 'json',
+      success: function (matches) {
+        if (typeof matches['status'] == 'undefined' || matches['status'] != 0) {
+          db.cache[searchString] = matches;
+          // Verify if these are still the matches the user wants to see
+          if (db.searchString == searchString) {
+            db.owner.found(matches);
+          }
+          db.owner.setStatus('found');
+        }
+      },
+      error: function (xmlhttp) {
+        alert(Drupal.ahahError(xmlhttp, db.uri));
+      }
+    });
+  }, this.delay);
+};
+
+/**
+ * Cancels the current keyword_autocomplete request
+ */
+Drupal.ACDB.prototype.cancel = function() {
+  if (this.owner) this.owner.setStatus('cancel');
+  if (this.timer) clearTimeout(this.timer);
+  this.searchString = '';
+};
Index: keyword_autocomplete.module
===================================================================
--- keyword_autocomplete.module	(revision 1614)
+++ keyword_autocomplete.module	(revision 1615)
@@ -90,19 +90,22 @@
       // The normal search form.
       if ($form_id == 'search_form' && $forms['search_form'] === 'search_form' && arg(0) != 'admin' && arg(1) != 'user') {
         $form['basic']['inline']['keys']['#autocomplete_path'] = 'search/keyword-autocomplete';
+        $form['basic']['inline']['keys']['#theme'] = 'keyword_autocomplete_textfield';
       }
 
       // The search form embedded in the theme.
       if ($form_id == 'search_theme_form' && $forms['search_theme_form'] === 'search_theme_form') {
         $form['search_theme_form']['#autocomplete_path'] = 'search/keyword-autocomplete';
+        $form['search_theme_form']['#theme'] = 'keyword_autocomplete_textfield';
       }
 
       // The form in the search block.
       if ($form_id == 'search_block_form' && $forms['search_block_form'] === 'search_block_form') {
         $form['search_block_form']['#autocomplete_path'] = 'search/keyword-autocomplete';
+        $form['search_block_form']['#theme'] = 'keyword_autocomplete_textfield';
       }
     }
-
+  
     // Set a custom submit handler to store the keywords in the database.
     $form['#submit'][] = 'keyword_autocomplete_search_form_submit';
   }
@@ -116,6 +119,11 @@
     'total_keyword' => array(
       'arguments' => array('count' => 0),
     ),
+    'keyword_autocomplete_textfield' => array(
+      'arguments' => array(
+        'element' => NULL,
+      ),
+    ),
   );
 }
 
@@ -481,3 +489,43 @@
   return system_settings_form($form);
 }
 
+/**
+ * Format an autocomplete that autosubmits.
+ *
+ * Borrowed shamelessly from finder_autocomplete from the finder module
+ *
+ * This was a result of user dissatisfaction with the default Drupal
+ * autocomplete method and allows for some of the configuration options that
+ * come with Autocomplete to function properly. The regular autocomplete
+ * textfields in finder just use theme_textfield by default.
+ *
+ * @param $element
+ *   The Forms API form element.
+ */
+function theme_keyword_autocomplete_textfield($element) {
+  $size = empty($element['#size']) ? '' : ' size="'. $element['#size'] .'"';
+  $maxlength = empty($element['#maxlength']) ? '' : ' maxlength="'. $element['#maxlength'] .'"';
+  $class = array('form-text');
+  $extra = '';
+  $output = '';
+
+  if (menu_valid_path(array('link_path' => $element['#autocomplete_path']))) {
+    $module = 'keyword_autocomplete';
+    drupal_add_js(drupal_get_path('module', $module) .'/'. $module .'.js');
+    $class[] = 'form-autocomplete';
+    $extra =  '<input class="keyword-autocomplete" type="hidden" id="'. $element['#id'] .'-autocomplete" value="'. check_url(url($element['#autocomplete_path'], array('absolute' => TRUE))) .'" disabled="disabled" />';
+  }
+  _form_set_class($element, $class);
+
+  if (isset($element['#field_prefix'])) {
+    $output .= '<span class="field-prefix">'. $element['#field_prefix'] .'</span> ';
+  }
+
+  $output .= '<input type="text"'. $maxlength .' name="'. $element['#name'] .'" id="'. $element['#id'] .'"'. $size .' value="'. check_plain($element['#value']) .'"'. drupal_attributes($element['#attributes']) .' />';
+
+  if (isset($element['#field_suffix'])) {
+    $output .= ' <span class="field-suffix">'. $element['#field_suffix'] .'</span>';
+  }
+  return theme('form_element', $element, $output) . $extra;
+}
+
