20 августа 2013 г.

В одном из проектов возникла необходимость реализовать многоуровневое меню OpenCart. Поскольку категорий было много, то их требовалось как-то выстроить в иерархическом порядке, чтобы посетителю было удобно перемещаться по нужным разделам сайта.

По умолчанию в OpenCart меню расчитано на два уровня с возможностью разделять список на столбцы. Но если категорий много, то выглядит это несколько громоздко, лучше информацию выдавать порциями, через выпадающие списки.

Для начала стоит задуматься о формировании категорий в иерархическом виде, для этого в файл модели категории ~/catalog/model/catalog/category.php добавим функцию getHierarchyCategories():

public function getHierarchyCategories() {

$categories = $this->cache->get('categories.hierarchy.' . (int)$this->config->get('config_language_id') . '.' . (int)$this->config->get('config_store_id'));
if(!$categories) {
$query = $this->db->query("SELECT * FROM " . DB_PREFIX . "category c LEFT JOIN " . DB_PREFIX . "category_description cd ON (c.category_id = cd.category_id) LEFT JOIN " . DB_PREFIX . "category_to_store c2s ON (c.category_id = c2s.category_id) WHERE cd.language_id = '" . (int)$this->config->get('config_language_id') . "' AND c2s.store_id = '" . (int)$this->config->get('config_store_id') . "'  AND c.status = '1' ORDER BY c.sort_order, LCASE(cd.name)");
$rows = $query->rows;
$num_rows = $query->num_rows;
$categories = array();
while($num_rows > 0) {
foreach ($rows as $key => $row) {
if ((int)$row['parent_id']) {
if ($this->addSubcategory($categories, $row)) {
unset($rows[$key]);
$num_rows--;
}
} else {
$categories[(int)$row['category_id']] = $row;
unset($rows[$key]);
$num_rows--;
}
}
$num_rows--;
}
$this->cache->set('categories.hierarchy.' . (int)$this->config->get('config_language_id') . '.' . (int)$this->config->get('config_store_id'), $categories);
}
return $categories;
}

Роль рекурсивной функции здесь выполняет функция addSubcategory(), её код представлен ниже, его также необходимо добавить в файл модели:

protected function addSubcategory(&$categories, $row) {
foreach($categories as $category) {
if ($category['category_id'] == $row['parent_id']) {
$categories[$row['parent_id']]['children'][$row['category_id']] = $row;
  return true;
} elseif (!empty($category['children'])) {
  if ($this->addSubcategory($category['children'], $row)) {
  $categories[$category['category_id']]['children'] = $category['children'];
  return true;
  }
}
}
return false;
}

Обратите внимание, что функцией getHierarchyCategories() используется кэширование, что не даёт ей злоупотреблять производительностью OpenCart.

Далее в контроллере "шапки" ~/catalog/controller/common/header.php добавляем функции, которые формируют массив данных для выпадающего меню из полученной иерархической структуры категорий:

protected function addCategories(&$data, $categories, $path = '') {
foreach($categories as $category) {
   $_path = $path ? ($path . '_' . $category['category_id']) : $category['category_id'];
$item = array(
'name'     => $category['name'],
'column'   => $category['column'] ? $category['column'] : 1,
'href'     => $this->url->link('product/category', 'path=' . $_path),
'sort_order'     => $category['sort_order']
);
if (!empty($category['children'])) {
$item['children'] = $this->addCategories($data, $category['children'], $_path);
}
if ($category['top']) {
unset($categories[$category['category_id']]);
$data[] = $item;
} else {
$categories[$category['category_id']] = $item;
}
}
usort($categories, array($this, 'compareSortOrder'));
return $categories;
}

function compareSortOrder($a, $b) {
return $a['sort_order'] - $b['sort_order'];
}


Примечание: Функция compareSortOrder($a, $b) используется для сортировки пунктов меню по порядку сортировки категорий.

В этом же файле строку кода $categories = $this->model_catalog_category->getCategories(0); заменяем на код ниже:
$categories = $this->model_catalog_category->getHierarchyCategories();
$this->addCategories($this->data['categories'], $categories);

Блок кода цикла foreach ($categories as $category) полностью удаляем или комментируем. На этом основные работы завершены, остаётся внести соответствующие изменения в шаблон меню ~/catalog/view/theme/default/template/common/header.tpl и файл стилей ~/catalog/view/theme/default/stylesheet/stylesheet.css.

В готовом решении последние шаги уже реализованы и многоуровневое меню (ограничений на количество уровней нет) построено без применения JavaScript, только CSS2. Меню совместимо со всеми популярными браузерами.

Новую версию решения можно заказать, связавшись с нами по электронной почте.

11 комментариев:

  1. Можно ли, в данном варианте многоуровнего меню, задать свои стили для каждого из уровня?

    ОтветитьУдалить
    Ответы
    1. Можно, если использовать селекторы CSS для дочерних объектов (http://www.w3.org/TR/CSS2/selector.html#child-selectors) или на этапе формирования структуры меню задавать имена классов для каждого уровня в самом шаблоне.

      Удалить
  2. Ответ на часто задаваемый вопрос о предоставлении ссылки на сайт с примером.

    Ссылок не предоставляю, поскольку все заинтересованные в этом решении просто копируют стили и пытаются дальше решить всё самостоятельно. Если появится свободное время, то сниму на эту разработку ознакомительное видео.

    ОтветитьУдалить
  3. где именно заканчивается Блок кода цикла foreach ($categories as $category). По какую строку удалять?
    И что значит остаётся внести соответствующие изменения в шаблон меню ~/catalog/view/theme/default/template/common/header.tpl и файл стилей ~/catalog/view/theme/default/stylesheet/stylesheet.css.
    Какие именно изменения????

    ОтветитьУдалить
  4. Описанное выше решение предназначено как минимум для тех, кто разбирается в программировании и знаком с устройством OpenCart. Это незаконченное решение, но достаточное, чтобы завершить его самостоятельно (или заказать его у автора).

    Что касается OpenCart 2.0, то данная версия, как официальный релиз, не рассматривается. Модули и решения, размещенные на этом сайте, касаются прежде всего OpenCart 1.5+.

    ОтветитьУдалить
  5. Разработана новая версия алгоритма формирования меню на PHP. Этот алгоритм использует всего один запрос к БД, иерархия выстраивается самостоятельно.

    ОтветитьУдалить
    Ответы
    1. Отличается повышенной производительностью, уровнями не ограничен.

      Удалить
  6. Этот комментарий был удален автором.

    ОтветитьУдалить
  7. А какое решение подойдёт для Опенкарт 2.2.?

    ОтветитьУдалить
    Ответы
    1. Здравствуйте! Все решения в этих блогах посвящены только OpenCart 1.5+ (http://opencartsoftware.blogspot.ru/p/blog-page.html)

      Удалить

  • RSS
  • Twitter
  • Youtube