WordPress Megamenü mit zusätzlichen Inhalten

Heute baue ich ein Megamenü in WordPress ohne Plugin.

Ziele

  • Normale, im Backend erstellte Menüs sollen verwendet werden können.
  • Einstellmöglichkeit pro Menüeintrag auf Ebene 0 für sein Submenü.
  • Zusätzliche individuelle Inhalte pro Submenü – Befüllung im Backend möglich.
  • Ausgabe in Widget „Individuelle Navigation“ möglich – Volle Systemunterstützung

Datenerfassung und Strukturen im Backend

Menü erstellen

Zuerst erstellen wir ein neues Menü in der WordPress Verwaltungsoberfläche. Das könnte man auch über die functions.php des Templates lösen, aber nachdem der Name des Menüs einen Inhalt darstellt und es sich anschließend als Widget ausgeben lässt, ist es mir lieber es über das Interface einzugeben.

Ich erstelle ein neues Menü und vergebe den Namen „main“

Dynamische Sidebar aus den Menüeinträgen generieren

Dazu initialisieren wir in der functions.php unseres Themes für jeden Level 0 Menüeintrag, der die Kriterien erfüllt, eine neue Sidebar. Sie wird nach der ID des Menüeintrags benannt. So können wir sie später auf der Widgets Seite gut wiederfinden.

Die Idee zu dieser Form der Architektur stammt von Domagoj Gojak. Jedoch ist sie so angepasst, dass statt der „theme_location“ der Menü Name abgefragt wird und Level 0 als zusätzliches Kriterium, damit nicht alle Menüeinträge abgefragt werden müssen, was Ausführungszeit spart.

Natürlich kann ich jeden Klassennamen verwenden – er muß im Menüinterface mit dem in der functions.php verwendeten übereinstimmen. Weiters kann man im eigenen Template den Klassennamen auch über die Theme-Settings einstellbar machen, das ist aber nicht Gegenstand dieses Artikels.

Kriterien
  • Ist in der obersten Menüebene – Level 0
  • Hat den HTML-Klassennamen „mega-dropdown“
Code
<?php

//...

// init sidebars
function yourTheme_sidebars_init() {

  if ( !function_exists('register_sidebar') ) {
    return;
  }
  // ...
  // .. other sidebars may be initialized here

	
  /**
   * Mega menu with widgets initialisation
   * --------------------
   * a menu named "main" has been created in wordpress backend
   * select the name at creation form and fill in here
   * select a class name that activates the mega sub-menu for specific menu items
   * Reference for this idea: https://slicejack.com/create-fully-custom-wordpress-mega-menu-no-plugins-attached/
   */
  $menu_name = 'main';
  $selected_class = 'mega-dropdown';
  $menu_main = wp_get_nav_menu_object($menu_name); // get the desired menu
  
  // wp_get_nav_menu_object() returns false if no result
  if ( $menu_main ) {
    // grab all menu items at once
    $menu_items = wp_get_nav_menu_items($menu_main->name);
    
    // wp_get_nav_menu_items() returns false if no result
    if ( $menu_items ) {
      // loop trough all menu items
      foreach ( $menu_items as $menu_item ) {
        
        // Skip if not level 0
        // adjust this if a 3rd level sub-menu can also be a mega menu
        if ( $menu_item->menu_item_parent === '0' ) {
          
          // only select menu items that has set a specific class name
          if ( in_array($selected_class, $menu_item->classes) ) {

            // add a sidebar to the wigdets admin page with the menu-item data
            // customize "before_widget" and "after_widget" to your needs
            register_sidebar(array(
              'id' => 'menu-' . $menu_item->ID,
              'name' => __('Mega Menu', 'yourTheme') . ' - ' . esc_html(wp_strip_all_tags($menu_item->title)),
              'before_widget'  =>   '<div id="%1$s" class="widget %2$s"><div class="inner">',
              'after_widget'   =>   '</div></div>',
              'before_title'   =>   '<h3 class="title">',
              'after_title'    =>   '</h3>',
            ));
          }
        }
      }
    }
  }
}

// init sidebars
add_action( 'init', 'yourTheme_sidebars_init' );

 CSS Klassen setzen

Damit die ausgewählten Menüpunkte alle Kriterien erfüllen, muß jeweils der Klassennamen eingetragen werden. Die Klassennamen werden voreingestellt nicht angezeigt – daher aktiviere ich deren Anzeige.

Zuerst wird das Feld für CSS Klassen aktiviert, dann der gewählte Klassenname eingetragen.

Ausgabe im Template

Anstatt ein spezifisches Template zu schreiben, filtern wir die Ausgabe des gewählten Menüs – die Filter kann man natürlich anpassen. Hier wird für das Menü mit dem Namen „main“ eine angepasste Walker Klasse ergänzt.

Da das Menü im Interface erzeugt wurde, hat es als Argument „menu“ beim Erstellen ein vollständigen „WP_Term“ erhalten. Den prüfen wir zuerst ab, damit keine Fehlermeldung bei „WP_Term->slug“ entstehen kann, mit dem der Menü Namen abgefragt werden kann.

Walker Aufruf mit Filter ergänzen

/**
 * identify menu by name and add custom walker class if applicaple
 * =====================
 * $args['menu'] is possibly a int|string|WP_Term
 * and filter 'wp_nav_menu_args' is called before it turns into WP_Term
 * 
 * @param  array  $args   Array of nav menu arguments. 
 * 
 * @see    by menu name   https://stackoverflow.com/questions/23375326#23375394 
 * @see    nav menu args  https://developer.wordpress.org/reference/functions/wp_nav_menu/#parameters
 * @see    is_a()         http://php.net/manual/en/function.is-a.php
 * @see    background     https://wordpress.stackexchange.com/questions/255244
 */

function yourTheme_nav_menu_args($args){
  $menu_name = 'main';

  // is $args['menu'] a WP_Term and is the menu name desired
  if ( is_a($args['menu'], 'WP_Term') && $args['menu']->slug == $menu_name ) {
    // Add custom walker class
    $args['walker'] = new Main_menu_nav_walker();
  }
  return $args;
}
  
// output mega menu in nav_menu
add_filter( 'wp_nav_menu_args', 'yourTheme_nav_menu_args');

Walker Klasse um die Ausgabe der Sidebars im Menü hinzuzufügen

Die Integration der Ausgabe der Sidebars funktioniert über die Walker Methoden „start_lvl“ und „end_lvl“ – hier kann beliebig HTML Markup vor und nach dem Submenü eingetragen werden.

Wir prüfen wieder die vorher definierten Kriterien – dazu muß der aktuelle Level 0 Eintrag in allen Methoden abrufbar sein, der in der Methode „start_el“ als „$item“ erhältlich ist. Er wird in einer neuen privaten Variable „$current_lvl0_item“ zwischengespeichert. Alle Methoden habe ich zuerst aus der Originalen Walker_Nav_Menu Klasse kopiert und da. wo es notwendig war, angepasst.

Da der Aufruf der Sidebar immer mittels „echo“ ausgegeben wird, sammeln wir die Ausgabe mit dem PHP Output Buffer und speichern diesen in die „$output“ Variable.

/** 
 * Custom walker class
 * ================
 * Integrate the output of the Markup and the sidebars into the desired menu
 * we need to save the current level 0 menu item from start_el() arguments and provide it to the other methods
 * use output buffer for the sidebar because widgets always "echo" their contents
 * 
 * @see  build custom walker          https://wordpress.stackexchange.com/questions/14037
 * @see  add container to sub menu    https://wordpress.stackexchange.com/questions/78121
 * @see  class Walker_Nav_Menu        https://developer.wordpress.org/reference/classes/walker_nav_menu/
 * @see  save item to other methods   https://wordpress.stackexchange.com/questions/62054
 * @see  sidebars echo contents       https://wordpress.stackexchange.com/questions/104177
 */

class Main_menu_nav_walker extends Walker_Nav_Menu{
  private $current_lvl0_item;                 // saves current level 0 menu item
  private $selected_class = 'mega-dropdown';  // class name for item selection
  private $dropdown_class = 'dropdown';       // class name for html output

  function start_el(&$output, $item, $depth = 0, $args = array(), $id = 0) {
    // execute start_el as is
    parent::start_el($output,$item,$depth,$args,$id);    
    // check for level 0
    if ( $depth == 0 ) {      
      // save item 
      $this->current_lvl0_item = $item;
    }
  }
  
  // first copied from default Walker_Nav_Menu and then edited
  // original part starts here
  function start_lvl( &$output, $depth = 0, $args = array() ) {
    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( 'sub-menu' );

    /**
    * Filters the CSS class(es) applied to a menu list element.
    *
    * @since 4.8.0
    *
    * @param array    $classes 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 = join( ' ', apply_filters( 'nav_menu_submenu_css_class', $classes, $args, $depth ) );
    $class_names = $class_names ? ' class="' . esc_attr( $class_names ) . '"' : '';
    // original part ends here
    
    // splitted from original
    $output .= $n . $indent;

    // only for the selected menu items at level 0
    if ( $depth == 0 && in_array($this->selected_class, $this->current_lvl0_item->classes) ) {
      // wrapper div for the sidebar and the sub menu
      $output .= '<div class="' . $this->dropdown_class . '">';
      // output the sidebar only if it has widgets
      if ( is_active_sidebar('menu-' . $this->current_lvl0_item->ID) ) {
        // sidebar wrapper div
        $output .= '<div class="sidebar sidebar-menu-' . $this->current_lvl0_item->ID . '">';
        // save save the contents of dynamic_sidebar() to output buffer
        ob_start();
        dynamic_sidebar('menu-' . $this->current_lvl0_item->ID);
        $output .= ob_get_contents();
        ob_end_clean();
        // end sidebar wrapper div
        $output .= '</div>';
      }
    }
    // splitted from original
    $output .= '<ul ' . $class_names . '>';
  }
  
  // first copied from default Walker_Nav_Menu and then edited
  // original part starts here
  function end_lvl( &$output, $depth = 0, $args = array() ) {
    if ( isset( $args->item_spacing ) && 'discard' === $args->item_spacing ) {
      $t = '';
      $n = '';
    } else {
      $t = "\t";
      $n = "\n";
    }
    $indent = str_repeat( $t, $depth );

    $output .= "$indent</ul>{$n}";
    // original part ends here

    // only for the selected menu items at level 0
    if ( $depth == 0 && in_array($this->selected_class, $this->current_lvl0_item->classes) ) {

      // end wrapper div for the sidebar and the sub menu
      $output .= '</div>';
    }
  }
}

Widgets einfügen

Damit Widgets angezeigt werden können, müssen auch welche eingefügt werden – das sollte jetzt so aussehen

Es können beliebige Widgets eingefügt werden, die Darstellung kann aber stark variieren.

Dadurch werden die Widgets im Quellcode vor dem Submenü ausgegeben

Ausgabe Quellcode

<li id="menu-item-20" class="mega-dropdown menu-item ...">
  <a href="...">Home</a>
  <div class="dropdown">
    <div class="sidebar sidebar-menu-20">
      <div id="text-2" class="widget widget_text">
        <div class="inner">
          <h3 class="title">Hey this is a cool dropdown</h3>
          <div class="textwidget">
            <p>I can write some more text here</p>
          </div>
        </div>
      </div>
      <div id="responsive_lightbox_image_widget-2" class="widget rl-image-widget">
        <div class="inner">
          <h3 class="title">A teaser in the menu saves the click</h3>
          <img class="rl-image-widget-image" src=".../myimage.jpg" width="100%" height="auto" title="My Image Title" alt="" style="float: left;" />
          <div class="rl-image-widget-text"></div>
        </div>
      </div>
    </div>
    <ul  class="sub-menu">
      <li id="menu-item-22" class="menu-item ...">
        <a href="...">Hallo Welt!</a>
      </li>
    	<li id="menu-item-23" class="menu-item ...">
    	  <a href="...">Allgemein</a>
      </li>
    </ul>
  </div>
</li>

Weitere Schritte

Jetzt kann es los gehen mit Styling und Javascript zum Öffnen/Schließen der neuen Mega-Dropdowns.

Discussion (No comments)

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.