Fuzzy autocomplete

For a small number of taxonomy terms, a select box can be used where the choices are explicitly displayed to the user.  But for larger numbers of terms using a select box becomes problematic.  Using a textfield with autocomplete is an obvious option when there are a large number of terms but it can be discarded because it is not robust to variations in spelling.  This post shows how in Drupal 7 a fuzzy autocomplete can be used to increase robustness.

There are three pieces to implementing a fuzzy autocomplete in Drupal 7.  The first is the form item, the second is defining the callback and the third and most important is a function that retrieves and returns the matches.

Start by defining a form in Drupal with a form item of type textfield and defining an autocomplete path.

/**
 * Fuzzy autocomplete taxonomy form.
 */
function fuzzy_complete_form($form, &$form_state) {

  $form['fuzzy'] = array(
    '#type' => 'textfield',
    '#title' => t('Fuzzy autocomplete'),
    '#size' => 30,
    '#description' => t('Choose a category'),
    '#autocomplete_path' => 'search/taxonomy/1',
  );

  return $form;
}

The autocomplete path has the vocabulary ID included in the path. The second part is defining the callback in the autocomplete path. Also included is defining a path to the form above.

/**
 * Implementation of hook_menu().
 */
function fuzzy_complete_menu() {
  $items = array();

  $items['search/taxonomy/%'] = array(
    'page callback' => 'taxonomy_autocomplete',
    'page arguments' => array(2),
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );

  $items['fuzzy'] = array(
    'title' => 'Fuzzy autocomplete',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('fuzzy_complete_form'),
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );
  return $items;
}

The final item is the possible matches.  In this part we split the string entered when it is equal or greater to four characters.  We search on both parts of the string and then combine the results.

/**
 * Fuzzy autocomplete for taxonomy.
 */
function taxonomy_autocomplete($vid, $search='') {
  $values = array();
  $count = array();
  if (strlen($search)<4) {
    $strings[] = $search;
  }
  else {
    $size = intval(ceil(strlen($search)/2));
    $strings[] = substr($search, 0, $size);
    $strings[] = substr($search, $size);
  }
  foreach ($strings as $key => $string) {
    if ($string=='') {
      continue;
    }
    $sql = 'SELECT tid, name FROM {taxonomy_term_data}
            WHERE vid=:vid AND name LIKE :name';
    $result = db_query($sql,
                       array('vid' => $vid, 'name' => '%' . $string . '%'));
    foreach ($result AS $data) {
      if (array_key_exists($data->tid, $count)) {
        $count[$data->tid]++;
      }
      else {
        $count[$data->tid] = 1;
        $values[$data->tid] = $data->name;
      }
    }
  }
  arsort($count);
  $num = 0;
  $matches = array();
  foreach ($count as $tid => $value) {
    $matches[$values[$tid]] = check_plain($values[$tid]);
    $num++;
    if ($num>=10) {
      break;
    }
  }
  drupal_json_output($matches);
}

Of course you will also need a vocabulary and terms.

Categories