Меню — сущность в WordPress, которая позволяет управлять навигационным меню проекта. Можно создавать много уровней вложенности и множество меню в разных местах страниц сайта.
Многие считают, что оно ограничено только этим, а функцию wp_nav_menu() нельзя кастомизировать.
Я решил развеять этот миф. Рассмотрим, как можно кастомизировать и расширить меню на примере. Для этого мы будем использовать Carbon Fields, о котором я уже писал здесь.
Посмотрите видео!
Основную часть кода нужно разместить в файле functions.php.
Важно помнить, что код нужно разделять на смысловые блоки и, в случае с WP, уже подключать их к functions.php. В конце статьи дам ссылку на репозиторий GitHub, где наглядно будет видно, где какой код можно разместить.
Проектирование меню и постановка задачи
Давайте создадим меню современного интернет-магазина. Пусть это будет магазин бытовой техники. Тогда меню будет иметь такую структуру:
- Каталог
- Телевизоры
- Full HD
- 4K
- Стиральные машины
- Фронтальные
- Вертикальные
- Плиты
- Газовые
- Электрические
- Микроволновки
- С разморозкой
- В грилем
- Мощность >700 ВТ
- Утюги
- С защитой от перегрева
- Керамическая подошва
- Пылесосы
- Классические
- Вертикальные
- Роботы-пылесосы
- Телевизоры
- О компании
- Доставка
- Способы оплаты
- Обмен, возврат и гарантия
- Услуги
- Доставка
- Монтаж
- Опт
- Новости
- Контакты
Раздел «Каталог» будет формироваться автоматически из категорий каталога WooCommerce. При этом мы сделаем так, чтобы при наведении на категорию, отображались товары из этой категории, которые укажет администратор сайта. И давайте сделаем так, чтобы у обычного меню второго уровня были иконки. Просто потому что мы можем 🙂
Вёрстка
Чтобы не тратить время на вёрстку, воспользуется готовыми решениями Bootstrap 5. Получится вот так:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-gH2yIJqKdNHPEq0n4Mqa/HGKIhSkIHeL5AyhkYV8i59U5AR6csBvApHHNl/vI1Bx" crossorigin="anonymous">
<link rel="stylesheet" href="style.css">
<title>Навигационное меню</title>
</head>
<body>
<div class="container">
<nav class="navbar navbar-expand-lg bg-light">
<div class="container-fluid">
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Каталог
</a>
<div class="dropdown-menu dropdown-catalog">
<div class="catalog_menu">
<div class="catalog_menu-flexbox">
<div class="catalog_menu-nav">
<ul>
<li>
<a data-category="#category_1" href="#">Телевизоры</a>
<ul>
<li> <a href="#">Full HD</a></li>
<li> <a href="#">4K</a></li>
</ul>
</li>
<li>
<a data-category="#category_2" href="#">Стиральные машины</a>
<ul>
<li> <a href="#">Фронтальные</a></li>
<li> <a href="#">Вертикальные</a></li>
</ul>
</li>
<li>
<a data-category="#category_3" href="#">Плиты</a>
<ul>
<li> <a href="#">Газовые</a></li>
<li> <a href="#">Электрические</a></li>
</ul>
</li>
<li>
<a data-category="#category_4" href="#">Микроволновки</a>
<ul>
<li> <a href="#">С разморозкой</a></li>
<li> <a href="#">В грилем</a></li>
<li> <a href="#">Мощность >700 ВТ</a></li>
</ul>
</li>
<li>
<a data-category="#category_5" href="#">Утюги</a>
<ul>
<li> <a href="#">С защитой от перегрева</a></li>
<li> <a href="#">Керамическая подошва</a></li>
</ul>
</li>
<li>
<a data-category="#category_6" href="#">Пылесосы</a>
<ul>
<li> <a href="#">Классические</a></li>
<li> <a href="#">Вертикальные</a></li>
<li> <a href="#">Роботы-пылесосы</a></li>
</ul>
</li>
</ul>
</div>
<div class="catalog_menu-products">
<!-- Тут уже сверстаны превью-карточки товаров -->
<div class="catalog_menu-category is-active" id="category_1">
<div class="catalog_menu-products-flexbox">
<div class="catalog_menu-product">
<div class="card">
<img src="https://images.unsplash.com/photo-1593784991095-a205069470b6?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=320&q=80" class="card-img-top">
<div class="card-body">
<h5 class="card-title">Название товара</h5>
<p class="card-text">Некоторое описание</p>
<div class="btn-group" role="group">
<button type="button" class="btn btn-primary">В корзину</button>
<a type="button" class="btn btn-outline-primary">Посмотреть</a>
</div>
</div>
</div>
</div>
<div class="catalog_menu-product">
<div class="card">
<img src="https://images.unsplash.com/photo-1593784991095-a205069470b6?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=320&q=80" class="card-img-top">
<div class="card-body">
<h5 class="card-title">Название товара</h5>
<p class="card-text">Некоторое описание</p>
<div class="btn-group" role="group">
<button type="button" class="btn btn-primary">В корзину</button>
<a type="button" class="btn btn-outline-primary">Посмотреть</a>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="catalog_menu-category" id="category_2">
<div class="catalog_menu-products-flexbox">
<div class="catalog_menu-product">
<div class="card">
<img src="https://images.unsplash.com/photo-1593784991095-a205069470b6?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=320&q=80" class="card-img-top">
<div class="card-body">
<h5 class="card-title">Название товара 2</h5>
<p class="card-text">Некоторое описание</p>
<div class="btn-group" role="group">
<button type="button" class="btn btn-primary">В корзину</button>
<a type="button" class="btn btn-outline-primary">Посмотреть</a>
</div>
</div>
</div>
</div>
<div class="catalog_menu-product">
<div class="card">
<img src="https://images.unsplash.com/photo-1593784991095-a205069470b6?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=320&q=80" class="card-img-top">
<div class="card-body">
<h5 class="card-title">Название товара 2</h5>
<p class="card-text">Некоторое описание</p>
<div class="btn-group" role="group">
<button type="button" class="btn btn-primary">В корзину</button>
<a type="button" class="btn btn-outline-primary">Посмотреть</a>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="catalog_menu-category" id="category_3">
<div class="catalog_menu-products-flexbox">
<div class="catalog_menu-product">
<div class="card">
<img src="https://images.unsplash.com/photo-1593784991095-a205069470b6?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=320&q=80" class="card-img-top">
<div class="card-body">
<h5 class="card-title">Название товара 3</h5>
<p class="card-text">Некоторое описание</p>
<div class="btn-group" role="group">
<button type="button" class="btn btn-primary">В корзину</button>
<a type="button" class="btn btn-outline-primary">Посмотреть</a>
</div>
</div>
</div>
</div>
<div class="catalog_menu-product">
<div class="card">
<img src="https://images.unsplash.com/photo-1593784991095-a205069470b6?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=320&q=80" class="card-img-top">
<div class="card-body">
<h5 class="card-title">Название товара 3</h5>
<p class="card-text">Некоторое описание</p>
<div class="btn-group" role="group">
<button type="button" class="btn btn-primary">В корзину</button>
<a type="button" class="btn btn-outline-primary">Посмотреть</a>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="catalog_menu-category" id="category_4">
<div class="catalog_menu-products-flexbox">
<div class="catalog_menu-product">
<div class="card">
<img src="https://images.unsplash.com/photo-1593784991095-a205069470b6?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=320&q=80" class="card-img-top">
<div class="card-body">
<h5 class="card-title">Название товара 4</h5>
<p class="card-text">Некоторое описание</p>
<div class="btn-group" role="group">
<button type="button" class="btn btn-primary">В корзину</button>
<a type="button" class="btn btn-outline-primary">Посмотреть</a>
</div>
</div>
</div>
</div>
<div class="catalog_menu-product">
<div class="card">
<img src="https://images.unsplash.com/photo-1593784991095-a205069470b6?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=320&q=80" class="card-img-top">
<div class="card-body">
<h5 class="card-title">Название товара 4</h5>
<p class="card-text">Некоторое описание</p>
<div class="btn-group" role="group">
<button type="button" class="btn btn-primary">В корзину</button>
<a type="button" class="btn btn-outline-primary">Посмотреть</a>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="catalog_menu-category" id="category_5">
<div class="catalog_menu-products-flexbox">
<div class="catalog_menu-product">
<div class="card">
<img src="https://images.unsplash.com/photo-1593784991095-a205069470b6?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=320&q=80" class="card-img-top">
<div class="card-body">
<h5 class="card-title">Название товара 5</h5>
<p class="card-text">Некоторое описание</p>
<div class="btn-group" role="group">
<button type="button" class="btn btn-primary">В корзину</button>
<a type="button" class="btn btn-outline-primary">Посмотреть</a>
</div>
</div>
</div>
</div>
<div class="catalog_menu-product">
<div class="card">
<img src="https://images.unsplash.com/photo-1593784991095-a205069470b6?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=320&q=80" class="card-img-top">
<div class="card-body">
<h5 class="card-title">Название товара 5</h5>
<p class="card-text">Некоторое описание</p>
<div class="btn-group" role="group">
<button type="button" class="btn btn-primary">В корзину</button>
<a type="button" class="btn btn-outline-primary">Посмотреть</a>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="catalog_menu-category" id="category_6">
<div class="catalog_menu-products-flexbox">
<div class="catalog_menu-product">
<div class="card">
<img src="https://images.unsplash.com/photo-1593784991095-a205069470b6?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=320&q=80" class="card-img-top">
<div class="card-body">
<h5 class="card-title">Название товара 6</h5>
<p class="card-text">Некоторое описание</p>
<div class="btn-group" role="group">
<button type="button" class="btn btn-primary">В корзину</button>
<a type="button" class="btn btn-outline-primary">Посмотреть</a>
</div>
</div>
</div>
</div>
<div class="catalog_menu-product">
<div class="card">
<img src="https://images.unsplash.com/photo-1593784991095-a205069470b6?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=320&q=80" class="card-img-top">
<div class="card-body">
<h5 class="card-title">Название товара 6</h5>
<p class="card-text">Некоторое описание</p>
<div class="btn-group" role="group">
<button type="button" class="btn btn-primary">В корзину</button>
<a type="button" class="btn btn-outline-primary">Посмотреть</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
О компании
</a>
<ul class="dropdown-menu">
<li><a href="#" class="dropdown-item"><i class="fa fa-star-o"></i> Доставка</a></li>
<li><a href="#" class="dropdown-item"><i class="fa fa-star-o"></i> Способы оплаты</a></li>
<li><a href="#" class="dropdown-item"><i class="fa fa-star-o"></i> Обмен, возврат и гарантия</a></li>
</ul>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Услуги
</a>
<ul class="dropdown-menu">
<li><a href="#" class="dropdown-item"><i class="fa fa-star-o"></i> Доставка</a></li>
<li><a href="#" class="dropdown-item"><i class="fa fa-star-o"></i> Монтаж</a></li>
</ul>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Опт</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Новости</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Контакты</a>
</li>
</ul>
</div>
</div>
</nav>
</div>
<!-- JavaScript Bundle with Popper -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-A3rJD856KowSb7dwlZdYEkO39Gagi7vIsF0jrRAoQmDKKtQBHUuLZ9AsSv4jD4Xa" crossorigin="anonymous"></script>
<script src="main.js"></script>
</body>
</html>
Конечно, такая нагромаждённая вёрстка меню — редкость. Да и товары в меню сейчас мало кто размещает. Но тут смысл в том, чтобы показать, что можно сделать действительно необычные вещи используя API WordPress, не костылируя костыли 🙂
Добавим стили в style.css:
body{
font-family: Arial;
}
.catalog_menu-category{
display: none;
}
.catalog_menu-category.is-active{
display: block;
}
.catalog_menu-flexbox{
display: flex;
}
.catalog_menu-nav{
flex: 0 0 40%;
max-width: 40%;
}
.catalog_menu-products{
flex: 0 0 60%;
max-width: 60%;
}
.catalog_menu-products-flexbox{
display: flex;
flex-wrap: wrap;
}
.catalog_menu-nav {
padding: 30px;
}
.catalog_menu-nav > ul {
display: flex;
flex-wrap: wrap;
}
.catalog_menu-nav > ul > li{
margin-right: 50px;
margin-bottom: 50px;
}
.catalog_menu-nav > ul > li > a{
font-weight: 700;
display: block;
margin-bottom: 10px;
}
.catalog_menu-nav > ul > li > * a:hover{
text-decoration: underline;
}
.catalog_menu-nav ul{
list-style-type: none;
padding: 0;
}
.catalog_menu-nav a{
color: #000;
text-decoration: none;
}
.catalog_menu-product {
margin-right: 30px;
}
И добавим JS, чтобы при наведении на категорию, показывались товары, которые относятся к ней. Файл main.js:
jQuery(function ($) {
let active_class = 'is-active';
$('a[data-category]').on('mouseover', function() {
let id = $(this).attr('data-category');
if ( !$(id).hasClass( active_class ) ) {
$('.catalog_menu-category').removeClass( active_class );
$(id).addClass( active_class );
}
});
});
Получилась такая штука:
Кастомизация управления меню: BackEnd
Настройки пунктов меню
Было-бы здорово, чтобы админ мог сам выбирать, из какого пункта будет выводиться меню каталога и какие иконки у какого пункта второго уровня будут установлены.
Для этого будем использовать плагин Carbon Fields. Вы можете скачать его здесь. Также советую ознакомиться с моей статьёй о нём.
Официальный сайт плагина — carbonfields.net
Сначала нужно создать кастомные поля ввода. Чтобы так сделать, укажем такой код:
<?php
/**
* Если Carbon Fields установлен через Composer - убедитесь, что также подключили его ядро.
* Если как плагин - то все и так будет работать.
**/
use Carbon_Fields\Container;
use Carbon_Fields\Field;
add_action( 'carbon_fields_register_fields', 'feodoraxis_menu_options' );
function feodoraxis_menu_options() {
Container::make( 'nav_menu_item', 'Дополнительно' )
->add_fields( [
Field::make( 'checkbox', 'feodoraxis-menu-is_catalog', 'Выводить каталог' ),
/**
* Тут я использую Font Awesome в качестве примера.
* Добавил только некоторые иконки для примера.
* Вы можете добавить все те, которые вам нужны из официальной документации
*/
Field::make( 'select', 'feodoraxis-menu-icon', 'Иконка' )
->set_options( [
'fa-music' => "Музыка",
'fa-search' => "Поиск",
'fa-envelope-o' => "Конверт",
'fa-heart' => "Сердце",
'fa-star' => "Звезда",
'fa-user' => "Пользователь",
'fa-film' => "Фильм",
] ),
] );
}
Дополнительные поля ввода появятся после того, как вы сохраните изменения в меню
Настройки каталога
По заданию нам нужно выводить в категориях каталога некоторые товары в этой категории. Мы не уточнили способ выбора товаров для каждой категории в меню. Поэтому можно поступить двумя способами:
- Сделать автоматический вывод двух случайных или последних товаров у каждой категории
- Дать возможность админу самому выбирать, какие товары нужно выводить в меню
Я считаю, что второй вариант лучше. Всегда лучше дать клиенту точное управление, если в ТЗ не указано иное.
Чтобы дать возможность админу выводить нужные товары, добавим у категорий товаров дополнительное поле, в котором он сможет указывать нужные ему товары.
<?php
/**
* Если пространства имен (use) уже были прописаны - повторно их указывать не нужно.
**/
use Carbon_Fields\Container;
use Carbon_Fields\Field;
add_action( 'carbon_fields_register_fields', 'feodoraxis_catalog_categories' );
function feodoraxis_catalog_categories() {
Container::make( 'term_meta', 'Настройки категорий' )
/**
* Я предполагаю, что для ИМ используется WooCommerce
**/
->where( 'term_taxonomy', '=', 'product_cat' )
->add_fields( [
Field::make( 'association', 'feodoraxis-products', 'Товары категории' )
->set_types( [
[
'type' => 'post',
'post_type' => 'product',
]
] )
->set_help_text( 'Укажите товары, которые хотите выводить в меню каталога' ) //Укажем подсказку админу
->set_max( 2 ) //У нас вёрстка не допускает больше двух товаров
] );
}
В итоге у нас получились такие настройки меню и настройки категорий товаров:
Кастомизация вёрстки
Теперь всю информацию нужно вывести используя нашу вёрстку.
Меню WordPress создаётся при помощи функци wp_nav_menu(); Чтобы мы могли кастомизировать HTML, нам потребуется Walker.
Walker — это специальный класс, определяющий внешний вид меню, которое выводит функция wp_nav_menu(). Но мы можем создать дочерний класс и с его помощью изменить внешний вид меню.
Для начала в шапке разместим функцию wp_nav_menu() и настроим её:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-gH2yIJqKdNHPEq0n4Mqa/HGKIhSkIHeL5AyhkYV8i59U5AR6csBvApHHNl/vI1Bx" crossorigin="anonymous">
<link rel="stylesheet" href="style.css">
<title>Навигационное меню</title>
</head>
<body>
<div class="container">
<nav class="navbar navbar-expand-lg bg-light">
<div class="container-fluid">
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<?php
wp_nav_menu( [
'theme_location' => 'primary',
'container' => false,
'menu_class' => 'navbar-nav me-auto mb-2 mb-lg-0',
'walker' => new Feodoraxis_Walker_Nav_Menu(), //Так вместо использования стандартного класса Walker_Nav_Menu(), мы указываем его дочерний класс, который будет кастомизировать
] );
?>
</div>
</div>
</nav>
</div>
<!-- JavaScript Bundle with Popper -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-A3rJD856KowSb7dwlZdYEkO39Gagi7vIsF0jrRAoQmDKKtQBHUuLZ9AsSv4jD4Xa" crossorigin="anonymous"></script>
<script src="main.js"></script>
</body>
</html>
Теперь создадим класс Feodoraxis_Walker_Nav_Menu — это будет дочерний класс, класса Walker_Nav_Menu().
Подключать класс нужно после настроек, которые мы создавали ранее.
<?php
class Feodoraxis_Walker_Nav_Menu extends Walker_Nav_Menu {
}
Теперь кастомизируем класс. Я удалю все комментарии разработчиков WordPress и оставлю свои — чтобы вам было проще ориентироваться в моих изменениях.
<?php
class Feodoraxis_Walker_Nav_Menu extends Walker_Nav_Menu {
public function start_lvl( &$output, $depth = 0, $args = null ) {
if ( isset( $args->item_spacing ) && 'discard' === $args->item_spacing ) {
$t = $n = '';
} else {
$t = "\t";
$n = "\n";
}
$indent = str_repeat( $t, $depth );
/**
* Заменим .sub-menu на .dropdown-menu
*/
$classes = array( 'dropdown-menu' );
$class_names = implode( ' ', apply_filters( 'nav_menu_submenu_css_class', $classes, $args, $depth ) );
$class_names = $class_names ? ' class="' . esc_attr( $class_names ) . '"' : '';
$output .= "{$n}{$indent}<ul$class_names>{$n}";
}
public function start_el( &$output, $data_object, $depth = 0, $args = null, $current_object_id = 0 ) {
$menu_item = $data_object;
if ( isset( $args->item_spacing ) && 'discard' === $args->item_spacing ) {
$t = '';
$n = '';
} else {
$t = "\t";
$n = "\n";
}
$indent = ( $depth ) ? str_repeat( $t, $depth ) : '';
$classes = empty( $menu_item->classes ) ? array() : (array) $menu_item->classes;
$classes[] = 'menu-item-' . $menu_item->ID;
/**
* Добавим Bootstrap-класс для пунктов меню первого уровня
*/
if ( $depth === 0 ) {
$classes[] = 'nav-item';
}
$args = apply_filters( 'nav_menu_item_args', $args, $menu_item, $depth );
$class_names = implode( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $menu_item, $args, $depth ) );
$class_names = $class_names ? ' class="' . esc_attr( $class_names ) . '"' : '';
$id = apply_filters( 'nav_menu_item_id', 'menu-item-' . $menu_item->ID, $menu_item, $args, $depth );
$id = $id ? ' id="' . esc_attr( $id ) . '"' : '';
$output .= $indent . '<li' . $id . $class_names . '>';
$atts = array();
$atts['title'] = ! empty( $menu_item->attr_title ) ? $menu_item->attr_title : '';
$atts['target'] = ! empty( $menu_item->target ) ? $menu_item->target : '';
if ( '_blank' === $menu_item->target && empty( $menu_item->xfn ) ) {
$atts['rel'] = 'noopener';
} else {
$atts['rel'] = $menu_item->xfn;
}
$atts['href'] = ! empty( $menu_item->url ) ? $menu_item->url : '';
$atts['aria-current'] = $menu_item->current ? 'page' : '';
/**
* Добавим классы ссылкам первого уровня
*/
if ( $depth === 0 ) {
$atts['class'] = 'nav-link';
/**
* Если есть дочерние пункты меню, или выводится меню каталога - добавим еще атриубты
*/
if ( in_array('menu-item-has-children', $menu_item->classes ) || carbon_get_nav_menu_item_meta( $menu_item->ID, 'feodoraxis-menu-is_catalog' ) ) {
$atts['class'] .= 'dropdown-toggle';
$atts['role'] = 'button';
$atts['data-bs-toggle'] = 'dropdown';
$atts['aria-expanded'] = 'false';
}
}
$atts = apply_filters( 'nav_menu_link_attributes', $atts, $menu_item, $args, $depth );
$attributes = '';
foreach ( $atts as $attr => $value ) {
if ( is_scalar( $value ) && '' !== $value && false !== $value ) {
$value = ( 'href' === $attr ) ? esc_url( $value ) : esc_attr( $value );
$attributes .= ' ' . $attr . '="' . $value . '"';
}
}
$title = apply_filters( 'the_title', $menu_item->title, $menu_item->ID );
$title = apply_filters( 'nav_menu_item_title', $title, $menu_item, $args, $depth );
$item_output = $args->before;
$item_output .= '<a' . $attributes . '>';
/**
* Выведем иконки
*/
if ( $depth > 0 ) {
$icon_class = carbon_get_nav_menu_item_meta( $menu_item->ID, 'feodoraxis-menu-icon');
if ( !empty( $icon_class ) ) {
$item_output .= '<i class="fa ' . $icon_class . '"></i> ';
}
}
$item_output .= $args->link_before . $title . $args->link_after;
$item_output .= '</a>';
/**
* Если это меню каталога, то выведем его при помощи дополнительного метода, который создадим прямо в этом классе
*/
if ( carbon_get_nav_menu_item_meta( $menu_item->ID, 'feodoraxis-menu-is_catalog' ) ) {
$item_output .= $this->get_catalog();
}
$item_output .= $args->after;
$output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $menu_item, $depth, $args );
}
/**
* @return string
*
* Метод вывода каталога товаров.
* Т.к. мы будем использовать функцию wc_get_template_part(), то нам потребуется кеширование данных на вывод
*/
protected function get_catalog():string {
global $post;
/**
* Сначала подготовим данные
*/
$categories = get_terms( [
'taxonomy' => 'product_cat',
'hide_empty' => false,
'orderby' => 'parent',
'exclude' => [15] //Не выводим Uncategorized
] );
$items = $products_categories = [];
/**
* Подготовим массив для удобного вывода
*/
foreach ( $categories as $category ) {
if ( $category->parent === 0 ) {
$items[ $category->term_id ] = [
'term_id' => $category->term_id,
'name' => $category->name,
'slug' => $category->slug,
'term_group' => $category->term_group,
'term_taxonomy_id' => $category->term_taxonomy_id,
'taxonomy' => $category->taxonomy,
'description' => $category->description,
'parent' => $category->parent,
'count' => $category->count,
'filter' => $category->filter,
];
/**
* Сразу получим товары для категорий
*/
$products = carbon_get_term_meta( $category->term_id, 'feodoraxis-products' );
if ( !empty( $products ) ) {
foreach ( $products as $product ) {
$products_categories[ $category->term_id ][] = $product['id'];
}
}
unset($products);
} else {
$items[ $category->parent ]['child'][ $category->term_id ] = [
'term_id' => $category->term_id,
'name' => $category->name,
'slug' => $category->slug,
'term_group' => $category->term_group,
'term_taxonomy_id' => $category->term_taxonomy_id,
'taxonomy' => $category->taxonomy,
'description' => $category->description,
'parent' => $category->parent,
'count' => $category->count,
'filter' => $category->filter,
];
}
}
/**
* Начало буферизации
*/
ob_start();
echo '<div class="dropdown-menu dropdown-catalog">
<div class="catalog_menu">
<div class="catalog_menu-flexbox">';
/**
* Выведем категории
*/
echo '<div class="catalog_menu-nav">
<ul>';
foreach ( $items as $item ) {
echo '<li>';
echo '<a href="' . get_term_link( $item['term_id'] ) . '" data-category="#category_' . $item['term_id'] . '">' . $item['name'] . '</a>';
/**
* В этом месте больше подошла-бы рекурсия, но мы упрощаем код для более простого понимания
*/
if ( isset( $item['child'] ) ) {
echo '<ul>';
foreach ( $item['child'] as $child ) {
echo '<li><a href="' . get_term_link( $child['term_id'] ) . '">' . $child['name'] . '</a></li>';
}
echo '</ul>';
}
echo '</li>';
}
echo '</ul>
</div>';
/**
* Выведем секции с превью-карточкам товаров
*/
echo '<div class="catalog_menu-products">';
if ( !empty( $products_categories ) ) {
foreach ( $products_categories as $key => $products ) {
echo '<div class="catalog_menu-category" id="category_' . $key . '">
<div class="catalog_menu-products-flexbox">';
/**
* Т.к. в стандартном шаблоне WC превью картоки товара выводятся при помощи списка - их нужно завернуть в родительский тег ul
*/
echo '<ul>';
foreach ( $products as $post ) {
setup_postdata( $post );
echo '<div class="catalog_menu-product">';
wc_get_template_part( 'content', 'product' );
echo '</div>';
}
wp_reset_postdata();
echo '</ul>
</div>
</div>';
}
}
echo '</div>';
/**
* Закрываем каталог меню
*/
echo '</div>
</div>
</div>';
/**
* Конец буферизации и вывод разметки
*/
$output = ob_get_contents();
ob_end_clean();
return $output;
}
}
Отлично! Мы дали все элементы управления и кастомизировали HTML, который выводит функция wp_nav_menu().
Вуаля! У нас получилось кастомизированное меню 🙂
Заключение и финальный код
В результате мы смогли прокачать меню в соответствии с заданием:
- Пункты меню второго уровня имеют свои иконки, которые может указывать администратор
- Админ может выбирать, какой пункт меню будет выводить категории меню
- Также у категорий в меню у нас выводятся товары, которые указывает администратор сайта.
Как по-мне, получилось круто. Вы можете кастомизировать этот код как хотите и использовать у себя 🙂
А посмотреть результат можно в этом репозитории GitHub — https://github.com/feodoraxis/menu-wordpress
Если у вас есть вопросы, или вы хотите покритиковать мою работу — велкам в комментарии!
Также подписывайтесь на мою группу в ВК и на YouTube-канал 🙂
Был полезно узнать! Нигде нету такой информации. Спасибо 🙂
Рад, что было полезно 🙂