Кастомизация autocomplete виджета

В taxonomy term reference поле есть виджет autocomplete, который использует Autocomplete jQuery UI и показывает имена уже существующих терминов при вводе желаемого имени термина. Но в некоторых случаях требуется выводить не только имя термина, но и некоторые другие данные о термине таксономии (к примеру, количество нод связанных с термином или описание термина). Подобный расширенный вывод автокомплита можно наблюдать на сайте stackexchange.com при добавлении тегов к новому вопросу.

autocomplete3.png

Чтобы добавить подобный функционал к taxonomy term reference полю в Drupal 7, нужно переопределить свойство #autocomplete_path для поля ввода термина. Для его изменения нужно в своем модуле имплементировать hook_form_alter():


/*
 * Implementation of hook_form_alter()
 */
function MYMODULE_form_alter(&$form, &$form_state, $form_id) {
  if ($form_id == 'article_node_form') {
    // Путь будет иметь аргумент field_tags (имя поля с авткомплитом)
    $form['field_tags']['und']['#autocomplete_path'] = 'custom/taxonomy/autocomplete/field_tags';
    $form['field_tags']['und']['#size'] = NULL;
  }
}

Далее определяем новый путь для автокомплита в hook_menu():


/*
 * Implements hook_menu().
 */
function MYMODULE_menu() {
  $items = array();
  $items['custom/taxonomy/autocomplete'] = array(
    'title' => 'Autocomplete taxonomy',
    'page callback' => 'custom_taxonomy_autocomplete',
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
  );
  return $items;
}

Теперь остается описать callback-функцию , которая будет формировать необходимый вывод для автокомплита:


function custom_taxonomy_autocomplete($field_name = '', $tags_typed = '') {
  $args = func_get_args();
  array_shift($args);
  $tags_typed = implode('/', $args);
  if (!($field = field_info_field($field_name)) || $field['type'] !== 'taxonomy_term_reference') {
    print t('Taxonomy field @field_name not found.', array('@field_name' => $field_name));
    exit;
  }
  $tags_typed = drupal_explode_tags($tags_typed);
  $tag_last = drupal_strtolower(array_pop($tags_typed));
  $term_matches = array();
  if ($tag_last != '') {
     $vids = array();
    $vocabularies = taxonomy_vocabulary_get_names();
    foreach ($field['settings']['allowed_values'] as $tree) {
      $vids[] = $vocabularies[$tree['vocabulary']]->vid;
    }

    $query = db_select('taxonomy_term_data', 'td');
    $query->addTag('translatable');
    $query->addTag('term_access');
    if (!empty($tags_typed)) {
      $query->condition('td.name', $tags_typed, 'NOT IN');
    }
    $query->fields('td', array('tid', 'name', 'description'))
    ->condition('td.vid', $vids)
    ->condition('td.name', '%' . db_like($tag_last) . '%', 'LIKE');
    $query->addExpression('COUNT(ti.tid)', 'count');
    $query->leftJoin('taxonomy_index', 'ti', 'td.tid=ti.tid');
    $query->groupBy('td.tid');
    // Добавляем сортировку по количеству связанных нод
    $query->orderBy('count', 'DESC');
    // Выводить будем 9 терминов 
    $query->range(0, 9);
    $tags_return = $query->execute();
    $prefix = count($tags_typed) ? drupal_implode_tags($tags_typed) . ', ' : '';

    foreach ($tags_return as $tag) {
      $n = $tag->name;
      if (strpos($tag->name, ',') !== FALSE || strpos($tag->name, '"') !== FALSE) {
        $n = '"' . str_replace('"', '""', $tag->name) . '"';
      }
      // Формируем необходимый html вывод из полученных данных
      $output = '';
      $output .= '<div class="tag-suggestions" >';
      $output .= '<span class="post-tag">' . check_plain($tag->name) . '</span>';
      $output .= '<span class="item-multiplier">&nbsp;×&nbsp;' . $tag->count . '</span>';
      $output .= '<p>' . $tag->description . '</p>';
      $output .= '</div>';
      $term_matches[$prefix . $n] = $output;
    }
  }
  drupal_json_output($term_matches);
}

С помощью css приводим к виду в соответствии с автокомплитом на сайте stackexchange.com:


input#edit-field-tags-und {
  width: 100%;
  max-width: 750px;
}
.post-tag {
  color: #707070 !important;
  background: #f8f8f8;
  border: 1px solid #ececec;
  padding: .2em .4em;
}
.post-tag,
.tag-suggestions p {
  margin: .4em 0 0 .4em;
}
.item-multiplier {
  color: #888;
}
#autocomplete {
  background: #fff;
}
#autocomplete ul {
  padding: .3em;
}
#autocomplete li {
  background: #fff;
  color: #000;
  white-space: normal;
  width: 33%;
  max-width: 250px;
  display: inline-block;
  padding: .5em 0;
  margin: .3em 0;
}
#autocomplete li.selected {
  background: #c0d8e6;
  color: #000;
}

В результате получаем вот такой виджет

autocomplete2.png