Вы здесь

Facet API. Вопросы. Темизация блока фильтра (Search service: [SERVER NAME])

0

Приветствую.

Разбираюсь со связкой Search API + Search API Database Search + Facet API. В основном все вопросы сняла вот эта статья, но остались ещё два вопроса, которые, пока что, не удалось победить.

Вопрос 1: Темизация блока фильтра (Facet API: Search service: [SERVER NAME])

После настройки фасета (если можно так сказать) по терминам таксономии из словаря «Категория», переместил появившийся блок сюда и дал ему видимость на определённой странице:

 Search service

Выводится и фильтруется всё отлично, но хотелось бы изменить обёртку. Сейчас сгенерированный код этого блока выглядит вот так:

<ul class="facetapi-facetapi-links facetapi-facet-field-category ui list facetapi-processed" id="facetapi-facet-search-apisearch-index-block-field-category">
    <li class="leaf first">
        <a>Link 1 (Count)</a>
    </li>
    ...
    <li class="leaf">
        <a>Link ... (...)</a>
    </li>
    ...
</ul>

Хотелось бы изменить на что-то типа этого:

<div class="facetapi-facetapi-links facetapi-facet-field-category ui list facetapi-processed" id="facetapi-facet-search-apisearch-index-block-field-category">
    <span>
        <a>Link 1</a><sup>(Count)</sup>
    </span>
    ...
    <span>
        <a>Link ...</a><sup>(...)</sup>
    </span>
    ...
</div>

То есть, чтобы был не ul > li список, а произвольная обёртка, например, в div > span. Порылся в модуле, нашёл вот такую функцию в файле ./facetapi/facetapi.theme.inc:

/**
 * Returns HTML for an inactive facet item.
 *
 * @param $variables
 *   An associative array containing the keys 'text', 'path', 'options', and
 *   'count'. See the l() and theme_facetapi_count() functions for information
 *   about these variables.
 *
 * @ingroup themeable
 */
function theme_facetapi_link_inactive($variables) {
  // Builds accessible markup.
  // @see http://drupal.org/node/1316580
  $accessible_vars = array(
    'text' => $variables['text'],
    'active' => FALSE,
  );
  $accessible_markup = theme('facetapi_accessible_markup', $accessible_vars);

  // Sanitizes the link text if necessary.
  $sanitize = empty($variables['options']['html']);
  $variables['text'] = ($sanitize) ? check_plain($variables['text']) : $variables['text'];

  // Adds count to link if one was passed.
  if (isset($variables['count'])) {
    $variables['text'] .= ' ' . theme('facetapi_count', $variables);
  }

  // Resets link text, sets to options to HTML since we already sanitized the
  // link text and are providing additional markup for accessibility.
  $variables['text'] .= $accessible_markup;
  $variables['options']['html'] = TRUE;
  return theme_link($variables);
}

Сама ссылка формируется через return theme_link($variables), это понятно. Но где формируется обёртка (li) для этой ссылки и общий контейнер (ul)? Никак не пойму. Подскажите, пожалуйста, кто сталкивался с этим (или просто знает).

Версия Drupal: 
7.x
Категория: 
Theming
Связанные проекты: 
Facet API
Вопрос задан 08.09.2015 - 15:45

Частично получилось решить вопрос со счётчиком (Count).

В файле ./facetapi/facetapi.theme.inc есть специальная функция для его обёртки:

/**
 * Returns HTML for the active facet item's count.
 *
 * @param $variables
 *   An associative array containing:
 *   - count: The item's facet count.
 *
 * @ingroup themeable
 */
function theme_facetapi_count($variables) {
  return '(' . (int) $variables['count'] . ')';
}

Также понял, как вынести счётчик (Count) за ссылку (переопределил в template.php):

function MYTHEME_facetapi_link_inactive($vars) {
  $accessible_vars = array(
    'text' => $vars['text'],
    'active' => FALSE,
  );
  $accessible_markup = theme('facetapi_accessible_markup', $accessible_vars);

  // Sanitizes the link text if necessary.
  $sanitize = empty($vars['options']['html']);
  $vars['text'] = ($sanitize) ? check_plain($vars['text']) : $vars['text'];

  // Resets link text, sets to options to HTML since we already sanitized the
  // link text and are providing additional markup for accessibility.
  $vars['text'] .= $accessible_markup;
  $vars['options']['html'] = TRUE;

  // Adds count to link if one was passed.
  if (isset($vars['count'])) {
    return theme_link($vars) . '&nbsp;' . theme('facetapi_count', $vars);
  }
  else {
    return theme_link($vars);
  }
}
Комментарий оставлен 08.09.2015 - 16:19

Первый вопрос еще актуален? Про замену ul li на div span?
Вы вместо тех фильтров, которые мы разбирали, решили поставить фасетный поиск? :-)
Почему?

Комментарий оставлен 09.09.2015 - 05:15

Евгений, приветствую!

Конечно же актуален! Причём как первый, так и второй мой вопрос. Как раз это и держит, чтобы развёрнуто ответить на «почему?» :)

Отвечу немного сумбурно. Те фильтры были нужны для одного проекта, а вот поиск с фасетами — для другого.. ну и плюс для саморазвития. Хотя, если получится темизнуть тот блок фасета (до такого состояния, как в последнем моём комментарии тут), то перенесу решение и в тот проект.

Да и вообще, люблю, когда есть готовые рабочие решения на все случаи жизни! Когда берёшь сразу пакет модулей, освежаешь в памяти (например, по решённым вопросам с этого сайта) то, что нужно настраивать/переопределять и уже не мучаешь себя и гугл «как-чего-куда?». В данной теме — лично я — снимаю давний «головняк» с различными фильтрами по материалам и организацией адекватного (гибкого, мощного) поиска по сайту.

Возможности, что я увидел в связке Search API + Facet API, меня устраивают больше, чем просто BEF + переопределение в template.php. Да и на первом проекте всё равно нужен поиск по сайту + разные фильтровые плюшки на будущее. Так что, одной темой — сразу несколько зайцев!

Надеюсь, что более-менее понятно написал ;)

Комментарий оставлен 09.09.2015 - 12:33

Попробуйте посмотреть функцию hook_facetapi_realm_info_alter.
Там в переменной $realm_info должен быть элемент 'element type', значение у которого 'links'. Поэтому для отображения списка вызывается функция темизации theme_links, которая выводит список в виде ul li. Попробуйте поменять $realm_info['element type'] на другую функцию: например, которую сделаете сами на основе theme_links. Создать новую функцию theme можно через hook_theme.
Для начала можно попробовать оставить $realm_info['element type'] пустой, чтобы вообще никакая функция не вызывалась - чтобы проверить, действительно ли в этом месте надо менять темизацию.

Комментарий оставлен 10.09.2015 - 05:18

Спасибо за ответ и направление куда копать!

Евгений – 10.09.2015 - 05:18:
Для начала можно попробовать оставить $realm_info['element type'] пустой, чтобы вообще никакая функция не вызывалась - чтобы проверить, действительно ли в этом месте надо менять темизацию.

Не помогло, так как он устанавливает это (и ещё вот это: default widget) значение по-умолчанию равным linksfacetapi_links соответственно), если оно не указано.. либо просто не тот хук. Попробовал, также, ещё вот с hook_facetapi_facet_info_alter() поиграться. Результат тот же — никакой.

Евгений, в теории-то — ясно, что надо в каком-либо theme_facetapi_?_alter() определить свой тип элемента и потом написать свой шаблон отображения.. но на практике, пока что, не очень получается. Делаю вот так:

function MYTHEME_facetapi_realm_info_alter(array &$realm_info) {
  $realm_info['block']['element type'] = 'my_element_type';
}

Далее пытаюсь сделать (по аналогии c theme_links()) свой тип элемента — theme_my_element_type():

function MYTHEME_my_element_type($variables) {
  $links = $variables ['my_element_type'];
  $attributes = $variables ['attributes'];
  $heading = $variables ['heading'];
  global $language_url;
  $output = '';

  if (count($links) > 0) {
    // Treat the heading first if it is present to prepend it to the
    // list of links.
    if (!empty($heading)) {
      if (is_string($heading)) {
        // Prepare the array that will be used when the passed heading
        // is a string.
        $heading = array(
          'text' => $heading,
          // Set the default level of the heading.
          'level' => 'h2',
        );
      }
      $output .= '<' . $heading ['level'];
      if (!empty($heading ['class'])) {
        $output .= drupal_attributes(array('class' => $heading ['class']));
      }
      $output .= '>' . check_plain($heading ['text']) . '</' . $heading ['level'] . '>';
    }

    $output .= '<div' . drupal_attributes($attributes) . '>';

    $num_links = count($links);
    $i = 1;

    foreach ($links as $key => $link) {
      $class = array($key);

      // Add first, last and active classes to the list of links to help out themers.
      if ($i == 1) {
        $class [] = 'first';
      }
      if ($i == $num_links) {
        $class [] = 'last';
      }
      if (isset($link ['href']) && ($link ['href'] == $_GET ['q'] || ($link ['href'] == '<front>' && drupal_is_front_page()))
         && (empty($link ['language']) || $link ['language']->language == $language_url->language)) {
        $class [] = 'active';
      }
      $output .= '<span' . drupal_attributes(array('class' => $class)) . '>';

      if (isset($link ['href'])) {
        // Pass in $link as $options, they share the same keys.
        $output .= l($link ['title'], $link ['href'], $link);
      }
      elseif (!empty($link ['title'])) {
        // Some links are actually not links, but we wrap these in <span> for adding title and class attributes.
        if (empty($link ['html'])) {
          $link ['title'] = check_plain($link ['title']);
        }
        $span_attributes = '';
        if (isset($link ['attributes'])) {
          $span_attributes = drupal_attributes($link ['attributes']);
        }
        $output .= '<span' . $span_attributes . '>' . $link ['title'] . '</span>';
      }

      $i++;
      $output .= "</span>\n";
    }

    $output .= '</div>';
  }

  return $output;
}

В результате, element type принимает значение my_element_type, но при этом theme_my_element_type() не подцепляется.

Поправьте меня, пожалуйста, чего не так делаю.

Комментарий оставлен 10.09.2015 - 09:51

У меня на локалке нету проекта с FacetAPI, поэтому не могу сразу дать готовое решение :-)
Если не получается перехватить через хук, то попробуйте перехватить рендер массив до вывода блока. Используйте template_preprocess_block. В теме создайте функцию THEME_preprocess_block(&$variables).
Распечатайте $variables через dpm (модуль devel).
В переменной $variables должен быть элемент массива 'content'. В нем будет что-то типа #theme = 'links'. Здесь можно вместо links задать свою функцию темы. Для начала попробуйте удалить #theme из массива. По идее ul li уже не должны выводиться.
Насчет своей функции темы. Назовите функцию MYTHEME_links__my_element_type. И $variables['content']['#theme'] = 'links__my_element_type'

Комментарий оставлен 10.09.2015 - 10:40

Самое интересное, что сделав theme_preprocess_block(&$variables) и потом print_r($variables), я нашёл вот это:

'field_category' => 
  array(
    '#theme' => 'item_list',
    ...
    ...

То есть не theme_links(), а через theme_item_list() что ли нужно ковырять? :)

Полистал гугл, оказывается у разработчиков были мысли на тему theme_facetapi_item_list(), но видимо не стали включать в релиз (судя по тому, что issue очень древний, не закрыт и последний комментарий, как бы, намекает на это).. ну или я чего-то не так перевёл гугл-транслейтером ;)

Комментарий оставлен 10.09.2015 - 11:20

Самое интересное, что сделав theme_preprocess_block(&$variables)

Имеете в виду MYTHEME_preprocess_block ?

Да, попробуйте item_list посмотреть. А что такое field_category? Это внутри $variables['content']?
Вместо print_r напишите dpm($variables), предварительно установив Devel. Там сразу видна структура.

Комментарий оставлен 10.09.2015 - 11:44

Имеете в виду MYTHEME_preprocess_block ?

Да, она самая.

А что такое field_category? Это внутри $variables['content']?

Это то, что удалось найти, в поиске по странице на запрос: #theme. Ну это логично, фасет же сделан по полю field_category.

Комментарий оставлен 10.09.2015 - 12:32

Временное (корявое, ужасное) решение всё-таки удалось найти.

В template.php определяем свой шаблон отображения theme_item_list():

function MYTHEME_item_list__mytheme($variables) {
  $items = $variables['items'];
  $title = $variables['title'];
  $type = $variables['type'];
  $attributes = $variables['attributes'];

  // Only output the list container and title, if there are any list items.
  // Check to see whether the block title exists before adding a header.
  // Empty headers are not semantic and present accessibility challenges.
  $output = '<div class="item-list">';
  if (isset($title) && $title !== '') {
    $output .= '<h3>' . $title . '</h3>';
  }

  if (!empty($items)) {
    $output .= "<div" . drupal_attributes($attributes) . '>';
    $num_items = count($items);
    $i = 0;
    foreach ($items as $item) {
      $attributes = array();
      $children = array();
      $data = '';
      $i++;
      if (is_array($item)) {
        foreach ($item as $key => $value) {
          if ($key == 'data') {
            $data = $value;
          }
          elseif ($key == 'children') {
            $children = $value;
          }
          else {
            $attributes [$key] = $value;
          }
        }
      }
      else {
        $data = $item;
      }
      if (count($children) > 0) {
        // Render nested list.
        $data .= theme_item_list(array('items' => $children, 'title' => NULL, 'type' => $type, 'attributes' => $attributes));
      }
      if ($i == 1) {
        $attributes ['class'][] = 'first';
      }
      if ($i == $num_items) {
        $attributes ['class'][] = 'last';
      }
      $output .= '<span' . drupal_attributes($attributes) . '>' . $data . "</span>\n";
    }
    $output .= "</div>";
  }
  $output .= '</div>';
  return $output;
}

Далее лезем в модуль — папка ./facetapi/plugins/facetapi и ищем файл widget_links.inc. В нём на 39 строчке видим следующее:

/**
   * Implements FacetapiWidget::execute().
   *
   * Transforms the render array into something that can be themed by
   * theme_item_list().
   *
   * @see FacetapiWidgetLinks::setThemeHooks()
   * @see FacetapiWidgetLinks::buildListItems()
   */
  public function execute() {
    $element = &$this->build[$this->facet['field alias']];

    // Sets each item's theme hook, builds item list.
    $this->setThemeHooks($element);
    $element = array(
      '#theme' => 'item_list',
      '#items' => $this->buildListItems($element),
      '#attributes' => $this->build['#attributes'],
    );
  }

Меняем '#theme' => 'item_list' на нашу функцию: '#theme' => 'item_list__mytheme'. Всё, цель достигнута. Всё как надо, даже лучше :)

Теперь узнать бы как, не затрагивая модуль, сделать тоже самое с #theme.

ADD:

Танцы с хуками модуля ничего не дали, так как скорее всего нужно написать свой субмодуль к Facet API, где будет класс FacetapiWidgetLinks (или, например, FacetapiWidgetInlineLinks) и там уже определить #theme как хочется.. но это пока не по моим силам/знаниям..

Комментарий оставлен 10.09.2015 - 12:41

Нет, без залезания в модуль — не работает.
Чую, что без субмодуля для фасет-виджета не обойтись тут..

Комментарий оставлен 10.09.2015 - 15:26

Мда. Третий день пошёл, как разбираюсь с этой проблемой..

Короче, чтобы не затягивать решение вопроса, решено было написать субмодуль для виджета Facet API. За пример взят был субмодуль Tagcloud Facets.

В основном, справился. Модуль отображается и можно выбрать его в настройках Facet API. Но, так как делаю субмодуль впервые (обычно как-то обходился хуками в template.php), есть мелкие вопросы по оформлению/написанию. А именно:

Почему в ./themes/MYTHEME/template.php вот это работает:

function MYTHEME_facetapi_count($variables) {
    return '<sup>' . (int) $variables['count'] . '</sup>';
}

А когда переношу в модуль ./modules/MYMODULE/mymodule.module, то ничего не работает:

function MYMODULE_facetapi_count($variables) {
    return '<sup>' . (int) $variables['count'] . '</sup>';
}

Аналогично ничего не отображается (фасет блок просто пропадает), когда переношу в свой модуль функцию из моего комментария выше (MYTHEME_item_list__mytheme($variables) с заменой MYTHEME на имя моего модуля).

Просто хочу максимум темизации фасет-блока запихать в файл моего модуля, чтобы потом просто «включил, настроил и ничего нигде дописывать руками в template.php не надо».

Подскажите чего не так делаю, если не трудно.

Комментарий оставлен 10.09.2015 - 16:10

У темы вес обычно больше, чем у модулей, поэтому код из темы выполняется после модулей.

А когда переношу в модуль ./modules/MYMODULE/mymodule.module, то ничего не работает:

Попробуйте изменить вес вашего модуля, чтобы он был больше, чем у фасетов.

Комментарий оставлен 14.09.2015 - 06:22

Ответы

1

Спустя бесчисленные часы, стену комментариев и огромного кол-ва прочитанного на d.org — я победил-таки :) Всё же без написания субмодуля не обошлось, но зато теперь есть готовое решение на все случаи жизни!

Ответ на вопрос №1: https://github.com/enjoyiacm/facetapi_inline_links

Facet API Inline Links — субмодуль (виджет) для Facet API, который позволяет организовать вывод элементов фасета (ссылок фильтра) в одну строчку, то есть используя обёртку DIV > SPAN, а не UL > LI (как в стандартном виджете). Также счётчик (count) фасета был вынесен за тег A.

Установка стандартная: скачать, залить в папку на хостинг, включить.
Далее перейти в настройки отображения фасета (./admin/config/search/search_api/index/[MY_SEARCH_INDEX]/facets) и выбрать в поле «Отображать виджет» — «Inline links». Настроить по вкусу. Готово!

Если добавить немного CSS, то можно сделать подобную красоту (использую Semantic UI):

Facet API Inline Links

При клике на фасет-ссылку, блок будет иметь такой вид (крестик слева — кликабелен):

Facet API Inline Links

Модуль пока что буду хранить на github. Любые предложения, пожелания, советы по улучшению и прочие полезные комментарии — с радостью рассмотрю.

P.S. Думаю, что на drupal.org выкладывать рано (или нет?), да и не делал я этого ни разу.

Ответ дан 10.09.2015 - 23:27