Instantly share code, notes, and snippets.
Last active
May 19, 2022 10:01
-
Save aaronsummers/4c769def1e4b77504346f335b943fa13 to your computer and use it in GitHub Desktop.
WordPress mege menu with featured images and alternative layout if the menu contains more than 4 items
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Nav Menu API: Walker_Nav_Menu class | |
* | |
* @package WordPress | |
* @subpackage Nav_Menus | |
* @since 4.6.0 | |
*/ | |
/** | |
* ANCHOR Add navigation menus | |
* | |
* @return void | |
*/ | |
function aaron_register_menus() { | |
register_nav_menus( | |
array( | |
'mega-menu' => __( 'Mega Menu' ), | |
) | |
); | |
} | |
add_action( 'init', 'aaron_register_menus' ); | |
/** | |
* ANCHOR Include a parent, child count into the menus objects ($item) | |
* @return integer | |
*/ | |
function aaron_wp_nav_menu_objects_filter( $sorted_menu_items, $args ) { | |
if( $args->theme_location == "mega-menu" ) | |
{ | |
foreach( $sorted_menu_items as &$item ) | |
{ | |
// Added the _ in front of the new properties as pseudo-namespacing, to avoid collisions with possible future official properties | |
$item->_children_count = 0; | |
for ($child_menu_items = 1, $total_children = count($sorted_menu_items); $child_menu_items <= $total_children; ++$child_menu_items) | |
{ | |
if ( $sorted_menu_items[$child_menu_items]->menu_item_parent == $item->ID ) | |
{ | |
$item->_children_count++; | |
} | |
} | |
} | |
foreach( $sorted_menu_items as &$item ) | |
{ | |
$item->_parent_children_count = 0; | |
for( $child_menu_items = 1, $total_children = count($sorted_menu_items); $child_menu_items <= $total_children; ++$child_menu_items ) | |
{ | |
if ( $item->menu_item_parent == $sorted_menu_items[$child_menu_items]->ID ) | |
{ | |
$item->_parent_children_count = $sorted_menu_items[$child_menu_items]->_children_count; | |
break; | |
} | |
} | |
} | |
unset($item); | |
return $sorted_menu_items; | |
} | |
} | |
// The 4th argument of add_filter allows a 2nd object to be passed to the function [$args] | |
add_filter('wp_nav_menu_objects', 'aaron_wp_nav_menu_objects_filter', 10, 2 ); | |
/** | |
* ANCHOR Core class used to implement an HTML list of nav menu items. | |
* | |
* @since 3.0.0 | |
* | |
* @see Walker | |
*/ | |
class Aaron_Walker_Mega_Menu extends Walker { | |
/** | |
* Set a parameter that can be set to allow us to close the mega menu | |
* @return boolean | |
*/ | |
private $has_sub_menu; | |
/** | |
* What the class handles. | |
* | |
* @since 3.0.0 | |
* @var string | |
* | |
* @see Walker::$tree_type | |
*/ | |
public $tree_type = array( 'post_type', 'taxonomy', 'custom' ); | |
/** | |
* Database fields to use. | |
* | |
* @since 3.0.0 | |
* @todo Decouple this. | |
* @var array | |
* | |
* @see Walker::$db_fields | |
*/ | |
public $db_fields = array( | |
'parent' => 'menu_item_parent', | |
'id' => 'db_id', | |
); | |
/** | |
* Starts the list before the elements are added. | |
* | |
* @since 3.0.0 | |
* | |
* @see Walker::start_lvl() | |
* | |
* @param string $output Used to append additional content (passed by reference). | |
* @param int $depth Depth of menu item. Used for padding. | |
* @param stdClass $args An object of wp_nav_menu() arguments. | |
*/ | |
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 ); | |
// Default class. | |
$classes = array( 'aaron-mega-menu__dropdown__submenu' ); | |
/** | |
* Filters the CSS class(es) applied to a menu list element. | |
* | |
* @since 4.8.0 | |
* | |
* @param string[] $classes Array of the CSS classes that are applied to the menu `<ul>` element. | |
* @param stdClass $args An object of `wp_nav_menu()` arguments. | |
* @param int $depth Depth of menu item. Used for padding. | |
*/ | |
$class_names = implode( ' ', apply_filters( 'nav_menu_submenu_css_class', $classes, $args, $depth ) ); | |
$class_names = $class_names ? ' class="' . esc_attr( $class_names ) . '"' : ''; | |
/** | |
* Only allow a single level within the mega menus | |
*/ | |
if ( $depth === 0 ) | |
{ | |
$output .= "{$n}{$indent}<ul$class_names>{$n}"; | |
} | |
} | |
/** | |
* Ends the list of after the elements are added. | |
* | |
* @since 3.0.0 | |
* | |
* @see Walker::end_lvl() | |
* | |
* @param string $output Used to append additional content (passed by reference). | |
* @param int $depth Depth of menu item. Used for padding. | |
* @param stdClass $args An object of wp_nav_menu() arguments. | |
*/ | |
public function end_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 ); | |
/** | |
* Only allow a single level within the mega menus | |
*/ | |
if ( $depth === 0 ) | |
{ | |
$output .= "$indent</ul>{$n}"; | |
} | |
} | |
/** | |
* Starts the element output. | |
* | |
* @since 3.0.0 | |
* @since 4.4.0 The {@see 'nav_menu_item_args'} filter was added. | |
* | |
* @see Walker::start_el() | |
* | |
* @param string $output Used to append additional content (passed by reference). | |
* @param WP_Post $item Menu item data object. | |
* @param int $depth Depth of menu item. Used for padding. | |
* @param stdClass $args An object of wp_nav_menu() arguments. | |
* @param int $id Current item ID. | |
*/ | |
public function start_el( &$output, $item, $depth = 0, $args = null, $id = 0 ) { | |
if ( isset( $args->item_spacing ) && 'discard' === $args->item_spacing ) { | |
$t = ''; | |
$n = ''; | |
} else { | |
$t = "\t"; | |
$n = "\n"; | |
} | |
$indent = ( $depth ) ? str_repeat( $t, $depth ) : ''; | |
$classes = empty( $item->classes ) ? array() : (array) $item->classes; | |
$classes[] = 'aaron-mega-menu__menu-item-' . $item->ID; | |
$anchor_class = 'aaron-mega-menu__menu-anchor'; | |
/** | |
* Sub-menu modifications | |
*/ | |
$aaron_sub_menu = array( | |
'icon' => '', | |
'class' => '' | |
); | |
if ( in_array('menu-item-has-children', $item->classes) ) | |
{ | |
// Add the submenu icon | |
$aaron_sub_menu = array( | |
'icon' => '<span class="aaron-mega-menu__submenu-icon"><i class="aaron-icon-chevron-pointing-down"></i></span>', | |
'class' => 'aaron-mega-menu__submenu--closed' | |
); | |
} | |
// Add a class to the submenu-item | |
if ( $item->menu_item_parent !== "0" ) | |
{ | |
$classes[] = 'aaron-mega-menu__dropdown__submenu-item'; | |
$anchor_class .= ' aaron-mega-menu__dropdown__submenu-anchor'; | |
} | |
/** | |
* Filters the arguments for a single nav menu item. | |
* | |
* @since 4.4.0 | |
* | |
* @param stdClass $args An object of wp_nav_menu() arguments. | |
* @param WP_Post $item Menu item data object. | |
* @param int $depth Depth of menu item. Used for padding. | |
*/ | |
$args = apply_filters( 'nav_menu_item_args', $args, $item, $depth ); | |
/** | |
* Filters the CSS classes applied to a menu item's list item element. | |
* | |
* @since 3.0.0 | |
* @since 4.1.0 The `$depth` parameter was added. | |
* | |
* @param string[] $classes Array of the CSS classes that are applied to the menu item's `<li>` element. | |
* @param WP_Post $item The current menu item. | |
* @param stdClass $args An object of wp_nav_menu() arguments. | |
* @param int $depth Depth of menu item. Used for padding. | |
*/ | |
$class_names = implode( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item, $args, $depth ) ); | |
$class_names = str_replace('menu-item ', 'aaron-mega-menu__menu-item ', $class_names); | |
$class_names = str_replace(' menu-item-', ' aaron-mega-menu__menu-item-', $class_names); | |
if ( $item->_children_count > 4 ) | |
{ | |
$class_names .= ' aaron-mega-menu--image-aside '; | |
} | |
else | |
{ | |
$class_names .= ' aaron-mega-menu--image-inline '; | |
} | |
$class_names = $class_names ? ' class="' . esc_attr( $class_names ) . ' ' . esc_attr( $aaron_sub_menu['class'] ) . '"' : ''; | |
/** | |
* Filters the ID applied to a menu item's list item element. | |
* | |
* @since 3.0.1 | |
* @since 4.1.0 The `$depth` parameter was added. | |
* | |
* @param string $menu_id The ID that is applied to the menu item's `<li>` element. | |
* @param WP_Post $item The current menu item. | |
* @param stdClass $args An object of wp_nav_menu() arguments. | |
* @param int $depth Depth of menu item. Used for padding. | |
*/ | |
$id = apply_filters( 'nav_menu_item_id', 'menu-item-' . $item->ID, $item, $args, $depth ); | |
$output .= $indent . '<li' . $class_names . ' data-child-count="' . $item->_children_count . '">'; | |
$atts = array(); | |
$atts['title'] = ! empty( $item->attr_title ) ? $item->attr_title : ''; | |
$atts['target'] = ! empty( $item->target ) ? $item->target : ''; | |
if ( '_blank' === $item->target && empty( $item->xfn ) ) { | |
$atts['rel'] = 'noopener'; | |
} else { | |
$atts['rel'] = $item->xfn; | |
} | |
$atts['class'] = esc_attr( $anchor_class ); | |
$atts['href'] = ! empty( $item->url ) ? $item->url : ''; | |
$atts['aria-current'] = $item->current ? 'page' : ''; | |
/** | |
* Filters the HTML attributes applied to a menu item's anchor element. | |
* | |
* @since 3.6.0 | |
* @since 4.1.0 The `$depth` parameter was added. | |
* | |
* @param array $atts { | |
* The HTML attributes applied to the menu item's `<>` element, empty strings are ignored. | |
* | |
* @type string $title Title attribute. | |
* @type string $target Target attribute. | |
* @type string $rel The rel attribute. | |
* @type string $href The href attribute. | |
* @type string $aria-current The aria-current attribute. | |
* } | |
* @param WP_Post $item The current menu item. | |
* @param stdClass $args An object of wp_nav_menu() arguments. | |
* @param int $depth Depth of menu item. Used for padding. | |
*/ | |
$atts = apply_filters( 'nav_menu_link_attributes', $atts, $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 . '"'; | |
} | |
} | |
/** This filter is documented in wp-includes/post-template.php */ | |
$title = apply_filters( 'the_title', $item->title, $item->ID ); | |
/** | |
* Filters a menu item's title. | |
* | |
* @since 4.4.0 | |
* | |
* @param string $title The menu item's title. | |
* @param WP_Post $item The current menu item. | |
* @param stdClass $args An object of wp_nav_menu() arguments. | |
* @param int $depth Depth of menu item. Used for padding. | |
*/ | |
$title = apply_filters( 'nav_menu_item_title', $title, $item, $args, $depth ); | |
/** | |
* ANCHOR Close off the mega menu | |
*/ | |
if ( $depth === 0 && $this->has_sub_menu == true ) | |
{ | |
$item_output = "</div>"; | |
$item_output .= "</div>"; | |
$item_output .= "</div>"; | |
$this->has_sub_menu = false; | |
} | |
/** | |
* ANCHOR Sub-menu image | |
*/ | |
if ( $depth > 0 && $this->has_sub_menu == true ) | |
{ | |
$post_id = $item->object_id; | |
$thumbnail = ''; | |
if ( has_post_thumbnail($post_id) ) | |
{ | |
$image_size = 'aaron-mega-menu'; | |
/** | |
* Our menu is designed to hold 4 items in columns with images. | |
* If our menu exceeds those four items, store the image url inside of a data attribute | |
* This data-attr URL is then grabbed with JS as the item is hovered and pushed into an image tag | |
*/ | |
if ( $item->_parent_children_count > 4 ) | |
{ | |
$image_url = get_the_post_thumbnail_url($post_id, $image_size); | |
$thumbnail = '<span class="aaron-mega-menu__aside-image" data-featured-image="' . $image_url . '"></span>'; | |
} | |
else | |
{ | |
// Get the attachment images ID which allows us to check the alt text | |
$image_id = get_post_thumbnail_id( $post_id ); | |
$image_alt = get_post_meta($image_id, '_wp_attachment_image_alt', true); | |
// Check if the image has an alt text tag set, if not use the image title | |
$alt_text = ( $image_alt ) ? $image_alt : get_the_title($image_id); | |
$thumbnail = get_the_post_thumbnail($post_id, $image_size, $attr = array('alt' => $alt_text, 'class' => 'aaron-mega-menu__image')); | |
} | |
} | |
/** | |
* ANCHOR Menu anchor, content, icon | |
*/ | |
$item_output = $args->before; | |
$item_output .= '<a' . $attributes . '>'; | |
$item_output .= $thumbnail; | |
$item_output .= '<span class="aaron-mega-menu__dropdown__link-title">'; | |
$item_output .= $args->link_before . $title . $args->link_after; | |
$item_output .= '</span>'; | |
$item_output .= '</a>'; | |
$item_output .= $args->after; | |
} | |
else | |
{ | |
/** | |
* ANCHOR Menu anchor, content, icon | |
*/ | |
$item_output = $args->before; | |
$item_output .= '<a' . $attributes . '>'; | |
$item_output .= $args->link_before . $title . $args->link_after; | |
$item_output .= '</a>'; | |
$item_output .= $aaron_sub_menu['icon']; | |
$item_output .= $args->after; | |
} | |
/** | |
* ANCHOR Open the mega menu | |
*/ | |
if ( $depth === 0 && in_array('menu-item-has-children', $item->classes) ) | |
{ | |
$item_output .= '<div class="aaron-mega-menu__dropdown">'; | |
$item_output .= '<div class="aaron-mega-menu__dropdown__container">'; | |
$item_output .= '<span class="aaron-mega-menu__dropdown__title">' . $title . '</span>'; | |
$item_output .= '<div class="aaron-mega-menu__dropdown__items">'; | |
$this->has_sub_menu = true; | |
} | |
/** | |
* Filters a menu item's starting output. | |
* | |
* The menu item's starting output only includes `$args->before`, the opening `<a>`, | |
* the menu item's title, the closing `</a>`, and `$args->after`. Currently, there is | |
* no filter for modifying the opening and closing `<li>` for a menu item. | |
* | |
* @since 3.0.0 | |
* | |
* @param string $item_output The menu item's starting HTML output. | |
* @param WP_Post $item Menu item data object. | |
* @param int $depth Depth of menu item. Used for padding. | |
* @param stdClass $args An object of wp_nav_menu() arguments. | |
*/ | |
$output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args ); | |
} | |
/** | |
* Ends the element output, if needed. | |
* | |
* @since 3.0.0 | |
* | |
* @see Walker::end_el() | |
* | |
* @param string $output Used to append additional content (passed by reference). | |
* @param WP_Post $item Page data object. Not used. | |
* @param int $depth Depth of page. Not Used. | |
* @param stdClass $args An object of wp_nav_menu() arguments. | |
*/ | |
public function end_el( &$output, $item, $depth = 0, $args = null ) { | |
if ( isset( $args->item_spacing ) && 'discard' === $args->item_spacing ) { | |
$t = ''; | |
$n = ''; | |
} else { | |
$t = "\t"; | |
$n = "\n"; | |
} | |
$output .= "</li>{$n}"; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment