Mega Menu Walker

10

Estoy tratando de crear un mega menú walker. Desafortunadamente, los caminantes escapan por completo de mi conocimiento de codificación. Realmente podría necesitar algo de ayuda para que funcione. Aquí están las características que necesito:

  • Envolver el segundo nivel <ul>en <section>. [COMPLETAR]
  • Cuando un usuario establece la clase "break" en un <li>segundo nivel <ul>, haga que <li>el comienzo sea nuevo <ul>. Si es el primero <li>en la lista, no haga nada para evitar la formación de listas vacías sin ordenar. [COMPLETAR]
  • Cuando un usuario establece la clase "widget" en un <li>en el primer nivel que tiene un sub <ul>, agregue un widget al final de ese <ul>. [COMPLETAR]
  • Agregue clase mega-menu-columns-#a <li>elementos de primer nivel que contengan menús desplegables con múltiples columnas y / o un widget. El # representa el número de <ul>elementos, +1 para el widget si existe. [COMPLETAR]

Tengo un poco de código para hacer algo de esto, pero no todo. Hay secciones recortadas a continuación:

Envuelva el segundo nivel <ul>en <section>:

function start_lvl(&$output, $depth = 0, $args = array()) {
    if ($depth == 0) {
        $output .= "<section>";
    }
    $output .= "<ul class=\"sub-menu\">";
}
function end_lvl(&$output, $depth = 0, $args = array()) {
    $output .= "</ul>";
    if ($depth == 0) {
        $output .= "</section>\n";
    }
}

Genere el widget HTML:

ob_start();
dynamic_sidebar("Navigation Callout");
$widget = ob_get_contents();
ob_end_clean();

El HTML de salida sería:

<ul>
    <li id="menu-item-1" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-has-children menu-item-1 mega-menu-columns-2">
        <a href="http://www.example.com/about/">
            About Us
        </a>
        <section>
            <ul class="sub-menu">
                <li id="menu-item-2" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-2">
                    <a href="http://www.example.com/about/company-profile/">
                        Company Profile
                    </a>
                </li>
                <li id="menu-item-3" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-3">
                    <a href="http://www.example.com/about/leadership-team/">
                        Leadership Team
                    </a>
                </li>
                <li id="menu-item-4" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-4">
                    <a href="http://www.example.com/about/professional-affiliations/">
                        Professional Affiliations
                    </a>
                </li>
            </ul>
            <ul class="sub-menu">
                <li id="menu-item-5" class="break menu-item menu-item-type-post_type menu-item-object-page menu-item-5">
                    <a href="http://www.example.com/about/clients/">
                        Clients
                    </a>
                </li>
                <li id="menu-item-6" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-6">
                    <a href="http://www.example.com/about/partnerships/">
                        Partnerships
                    </a>
                </li>
            </ul>
        </section>
    </li>
    <li id="menu-item-7" class="widget menu-item menu-item-type-post_type menu-item-object-page menu-item-has-children menu-item-7 mega-menu-columns-3">
        <a href="http://www.example.com/services/">
            Services
        </a>
        <section>
            <ul class="sub-menu">
                <li id="menu-item-8" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-8">
                    <a href="http://www.example.com/services/civil-engineering/">
                        Civil Engineering
                    </a>
                </li>
                <li id="menu-item-9" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-9">
                    <a href="http://www.example.com/services/land-planning/">
                        Land Planning
                    </a>
                </li>
                <li id="menu-item-10" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-10">
                    <a href="http://www.example.com/services/surveying/">
                        Surveying
                    </a>
                </li>
            </ul>
            <ul class="sub-menu">
                <li id="menu-item-11" class="break menu-item menu-item-type-post_type menu-item-object-page menu-item-11">
                    <a href="http://www.example.com/services/information-technology/">
                        Information Technology
                    </a>
                </li>
                <li id="menu-item-12" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-12">
                    <a href="http://www.example.com/services/subsurface-utility-engineering/">
                        Subsurface Utility Engineering
                    </a>
                </li>
            </ul>
            <aside>
                <h6>Widget Title</h6>
                <p>Maecenas quis semper arcu. Quisque consequat risus nisi. Sed venenatis urna porta eros malesuada euismod. Nulla sollicitudin fringilla posuere. Nulla et tellus eu nisi sodales convallis non vel tellus.</p>
            </aside>
        </section>
    </li>
    <li id="menu-item-13" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-15">
        <a href="http://www.example.com/contact/">
            Contact Us
        </a>
    </li>
</ul>

ACTUALIZACIÓN: Mis contadores me dan pena. Solo cuentan después de que se haya generado el submenú, lo que no me ayuda. Vea esta captura de pantalla para entender lo que quiero decir:

Los números superiores están siendo introducidos start_el. Se están introduciendo los números inferiores end_el. Como puede ver, los números superiores no cuentan mi .breakscomo deberían. Cuentan la clase de widget porque se están contando $depth = 0. ¡Que alguien me salve de esta horrible!

// mega menu walker
/*
    ONE REMAINING BUG:
    - Need to add class to LI containing mega-menu-columns-#
*/
class megaMenuWalker extends Walker_Nav_Menu {
    private $column_limit = 3; /* needs to be set for each site */
    private $show_widget = false;
    private $column_count = 0;
    static $li_count = 0;
    function start_el(&$output, $item, $depth = 0, $args = array(), $id = 0) {
        $classes = empty($item->classes) ? array() : (array) $item->classes;
        $item_id = $item->ID;
        if ($depth == 0) self::$li_count = 0;
        if ($depth == 0 && in_array("widget", $classes)) {
            $this->show_widget = true;
            $this->column_count++;
        }
        if ($depth == 1 && self::$li_count == 1) {
            $this->column_count++;
        }
        if ($depth == 1 && in_array("break", $classes) && self::$li_count != 1 && $this->column_count < $this->column_limit) {
            $output .= "</ul><ul class=\"sub-menu\">";
            $this->column_count++;
        }
        if ($depth == 0 && $this->column_count > 0) {
            $mega_menu_class = " mega-menu-columns-" . $this->column_count;
        }
        $class_names = join(" ", apply_filters("nav_menu_css_class", array_filter($classes), $item));
        $class_names = " class=\"" . esc_attr($class_names . $mega_menu_class) . "\"";
        $output .= sprintf(
            "<li id=\"menu-item-%s\"%s><a href=\"%s\">%s</a>",
            $item_id,
            $class_names,
            $item->url,
            $item->title
        );
        self::$li_count++;
    }
    function start_lvl(&$output, $depth = 0, $args = array()) {
        if ($depth == 0) {
            $output .= "<section>";
        }
        $output .= "<ul class=\"sub-menu\">";
    }
    function end_lvl(&$output, $depth = 0, $args = array()) {
        $output .= "</ul>";
        if ($depth == 0) {
            if ($this->show_widget) {
                ob_start();
                dynamic_sidebar("Navigation Callout");
                $widget = ob_get_contents();
                ob_end_clean();
                $output .= $widget;
                $this->show_widget = false;
            }
            $output .= "</section>";
        }
    }
    function end_el(&$output, $item, $depth = 0, $args = array(), $id = 0) {
        if ($depth == 0 && $this->column_count > 0) {
            /* needs to be added to opening level 0 li */
            $column_count_class = " mega-menu-columns-" . $this->column_count;
            $output .= $column_count_class;
            /* end */
            $this->column_count = 0;
        }
        $output .= "</li>";
    }
}   

ACTUALIZACIÓN 2: Aquí hay un ejemplo de salida con comentarios que describen cómo la mega-menu-columns-clase debe contar las cosas:

<ul>
    <!-- +1 because this has a class of "widget" -->
    <li id="menu-item-1" class="widget menu-item menu-item-type-post_type menu-item-object-page menu-item-has-children menu-item-1 mega-menu-columns-3">
        <a href="http://www.example.com/about/">
            About Us
        </a>
        <!-- +1 because a drop down exists -->
        <!-- gets added by my walker -->
        <section>
        <!-- end gets added by my walker -->
            <ul class="sub-menu">
                <!-- +0 because this "break" is the first child -->
                <li id="menu-item-2" class="break menu-item menu-item-type-post_type menu-item-object-page menu-item-2">
                    <a href="http://www.example.com/about/company-profile/">
                        Company Profile
                    </a>
                    <ul>
                        <!-- +0 because this "break" is in level 2 -->
                        <li id="menu-item-3" class="break menu-item menu-item-type-post_type menu-item-object-page menu-item-3">
                            <a href="http://www.example.com/about/our-team/">
                                Our Team
                            </a>
                        </li>
                    </ul>
                </li>
                <li id="menu-item-4" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-4">
                    <a href="http://www.example.com/about/leadership-team/">
                        Leadership Team
                    </a>
                </li>
                <li id="menu-item-5" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-5">
                    <a href="http://www.example.com/about/professional-affiliations/">
                        Professional Affiliations
                    </a>
                </li>
            <!-- gets added by my walker -->
            </ul>
            <ul class="sub-menu">
            <!-- end gets added by my walker -->
                <!-- +1 because this "break" is in level 1 and not the first child -->
                <li id="menu-item-6" class="break menu-item menu-item-type-post_type menu-item-object-page menu-item-6">
                    <a href="http://www.example.com/about/clients/">
                        Clients
                    </a>
                </li>
                <li id="menu-item-7" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-7">
                    <a href="http://www.example.com/about/partnerships/">
                        Partnerships
                    </a>
                </li>
            </ul>
            <!-- gets added by my walker for .widget -->
            <section>
                <header>
                    <h1>Widget Title</h1>
                </header>
                <p>This is a widget. It was hard to make appear!</p>
            </section>
            <!-- end gets added by my walker for .widget -->
        <!-- gets added by my walker -->
        </section>
        <!-- end gets added by my walker -->
    </li>
</ul>

ACTUALIZACIÓN: Aquí está mi Walker y Funciones finales. Esto hace exactamente lo que quería que hiciera. ¡Gracias por la ayuda!

// mega menu walker
class megaMenuWalker extends Walker_Nav_Menu {
    private $column_limit = 3;
    private $show_widget = false;
    private $column_count = 0;
    static $li_count = 0;
    function start_el(&$output, $item, $depth = 0, $args = array(), $id = 0) {
        $classes = empty($item->classes) ? array() : (array) $item->classes;
        $item_id = $item->ID;
        if ($depth == 0) {
            self::$li_count = 0;
        }
        if ($depth == 0 && in_array("widget", $classes)) {
            $this->show_widget = true;
            $this->column_count++;
        }
        if ($depth == 1 && self::$li_count == 1) {
            $this->column_count++;
        }
        if ($depth == 1 && in_array("break", $classes) && self::$li_count != 1 && $this->column_count < $this->column_limit) {
            $output .= "</ul><ul class=\"sub-menu\">";
            $this->column_count++;
        }
        $class_names = join(" ", apply_filters("nav_menu_css_class", array_filter($classes), $item)); // set up the classes array to be added as classes to each li
        $class_names = " class=\"" . esc_attr($class_names) . "\"";
        $output .= sprintf(
            "<li id=\"menu-item-%s\"%s><a href=\"%s\">%s</a>",
            $item_id,
            $class_names,
            $item->url,
            $item->title
        );
        self::$li_count++;
    }
    function start_lvl(&$output, $depth = 0, $args = array()) {
        if ($depth == 0) {
            $output .= "<section>";
        }
        $output .= "<ul class=\"sub-menu\">";
    }
    function end_lvl(&$output, $depth = 0, $args = array()) {
        $output .= "</ul>";
        if ($depth == 0) {
            if ($this->show_widget) {
                ob_start();
                dynamic_sidebar("Navigation Callout");
                $widget = ob_get_contents();
                ob_end_clean();
                $output .= $widget;
                $this->show_widget = false;
            }
            $output .= "</section>";
        }
    }
    function end_el(&$output, $item, $depth = 0, $args = array(), $id = 0) {
        if ($depth == 0 && $this->column_count > 0) {
            $this->column_count = 0;
        }
        $output .= "</li>";
    }
}

// add mega-menu-columns-# classes
function add_column_number($items, $args) {
    static $column_limit = 3;
    static $post_id = 0;
    static $x_key = 0;
    static $column_count = 0;
    static $li_count = 0;
    $tmp = array();
    foreach($items as $key => $item) {
        if (0 == $item->menu_item_parent) {
            $x_key = $key;
            $post_id = $item->ID;
            $column_count = 0;
            $li_count = 0;
            if (in_array("widget", $item->classes, 1)) {
                $column_count++;
            }
        }
        if ($post_id == $item->menu_item_parent) {
            $li_count++;
            if ($column_count < $column_limit && $li_count == 1) {
                $column_count++;
            }
            if (in_array("break", $item->classes, 1) && $li_count > 1 && $column_count < $column_limit) {
                $column_count++;
            }
            $tmp[$x_key] = $column_count;
        }
    }
    foreach($tmp as $key => $value) {
        $items[$key]->classes[] = sprintf("mega-menu-columns-%d", $value);
    }
    unset($tmp);
    return $items;
};

// add the column classes
add_filter("wp_nav_menu_args", function($args) {
    if ($args["walker"] instanceof megaMenuWalker) {
        add_filter("wp_nav_menu_objects", "add_column_number");
    }
    return $args;
});

// stop the column classes function
add_filter("wp_nav_menu", function( $nav_menu ) {
    remove_filter("wp_nav_menu_objects", "add_column_number");
    return $nav_menu;
});
JacobTheDev
fuente
1
¡Esto es súper increíble y es exactamente lo que he querido construir durante mucho tiempo! No estaba seguro de cómo manejar la creación de nuevas columnas y tal, así que realmente me encanta la idea de simplemente establecer un nombre de clase en los elementos para crear nuevas columnas y también la capacidad de agregar áreas de widgets, ¡simplemente genial! Sin embargo, tengo algunos problemas con su código terminado ... genera el HTML corregido, sin embargo, recibo este error de PHP ... Warning: Missing argument 2 for add_column_number() ¿Encontró este problema?
JasonDavis
No lo creo, parece estar funcionando bien en los dos sitios en los que lo he usado hasta ahora. He actualizado el código anterior, en caso de que haya cambiado algo al implementarlo. Inténtalo de nuevo y avísame si aún no funciona; No soy muy bueno con PHP, pero me encantaría intentar ayudarte :)
JacobTheDev
1
Lo que tuve que hacer fue cambiar esto function add_column_number($items, $args) {para function add_column_number($items) {eliminarlo $argsy funciona bien para mí con ese cambio, ¡bastante extraño! Gracias por compartir su código, aunque es justo lo que he necesitado durante mucho tiempo
JasonDavis
No hay problema, sé lo difícil que es hacer esto, por eso compartí mi código completo :) ¡Me alegra que haya ayudado!
JacobTheDev

Respuestas:

7

Si entiendo la configuración del problema correctamente, podría intentar hacer el recuento de la clase de interrupción y widget dentro del wp_nav_menu_objectsfiltro.

Aquí hay un ejemplo actualizado, está bastante expandido debido a la parte de depuración adicional:

add_filter( 'wp_nav_menu_objects', 
    function( $items, $args  ) {

        // Only apply this for the 'primary' menu:    
        if( 'primary' !== $args->theme_location ) 
            return $items;

        // Here "x_" means the latest root li (depth 0)
        static $x_pid           = 0;      // post ID of the latest root li (depth 0) 
        static $x_key           = 0;      // array key of the latest root li (depth 0)
        static $x_cols          = 0;      // n breaks or widgets gives n+1 columns  
        static $x_has_dropdown  = false;  // if the latest root li (depth 0) has dropdown

        // Internals:
        $tmp            = array();  
        $debug_string   = '';
        $show_debug     = true;  // Edit this to your needs:

        foreach( $items as $key => $item )
        {
            // Debug:
            $debug                              = array();
            $debug['ID']                        = $item->ID;
            $debug['title']                     = $item->title;
            $debug['key']                       = $key;
            $debug['x_key']                     = $x_key;
            $debug['depth']                     = '';
            $debug['menu_item_parent']          = $item->menu_item_parent;
            $debug['has_widget_class']          = 0;
            $debug['is_depth_1_first_child']    = 0;
            $debug['x_has_dropdown']            = 0;
            $debug['has_break_class']           = 0;
            $debug['x_cols_increase']           = 0;

            // Collect columns increaments:
            $inc = 0;

            // Depth 0:
            if( 0 == $item->menu_item_parent )
            {
                $debug['depth'] = 0;

                // Resets:
                $x_key          = $key;
                $x_pid          = $item->ID;                            
                $x_cols         = 0;
                $x_has_dropdown = false;

                // If widget class exists:
                if( in_array( 'widget', $item->classes, 1 ) )
                {
                    $debug['has_widget_class'] = '1';
                    $inc++; 
                }       
            }

            // Depth 1:
            if( $x_pid == $item->menu_item_parent )
            {   
                $debug['depth'] = 1;

                // Increase the columns count for an existing dropdown:
                if( ! $x_has_dropdown )
                {
                    $inc++;
                    $x_has_dropdown = true;
                }

                // Check for the 'break' class: 
                if( in_array( 'break', $item->classes, 1 ) )
                {
                    $debug['x_has_break_class'] = 1;

                    // First li child:
                    if( $x_key+1 == $key+0 )
                    {
                        $debug['is_depth_1_first_child'] = 1;
                    }
                    else
                    {
                        $debug['is_depth_1_first_child'] = 0;
                        $inc++; 
                    }
                }

                $t[$x_key] = $x_cols;
            }           

            $debug['x_has_dropdown'] = (int) $x_has_dropdown;

            // Increase the columns count:
            $debug['x_cols_increase'] = $inc;           
            $x_cols += $inc;
            $debug['x_cols'] = $x_cols;

            // Collect the debug:
            $debug_string .= print_r( $debug, 1 );
        } // end foreach

        // Show debug info:
        if( $show_debug ) 
            printf( "<!-- debug: %s -->", $debug_string );

        // Insert the new 'mega menu' class to the corresponding menu object:
        foreach( $t as $key => $value )
        {
            $items[$key]->classes[] = sprintf( 'mega-menu-columns-%d', $value );
        }

        return $items;

    }
, PHP_INT_MAX, 2 );

Con su árbol de menú actual, obtengo esta información de depuración:

<!-- debug: Array
(
    [ID] => 3316
    [title] => About Us
    [key] => 1
    [x_key] => 0
    [depth] => 0
    [menu_item_parent] => 0
    [has_widget_class] => 1
    [is_depth_1_first_child] => 0
    [x_has_dropdown] => 0
    [has_break_class] => 0
    [x_cols_increase] => 1
    [x_cols] => 1
)
Array
(
    [ID] => 3317
    [title] => Company Profile
    [key] => 2
    [x_key] => 1
    [depth] => 1
    [menu_item_parent] => 3316
    [has_widget_class] => 0
    [is_depth_1_first_child] => 1
    [x_has_dropdown] => 1
    [has_break_class] => 0
    [x_cols_increase] => 1
    [x_has_break_class] => 1
    [x_cols] => 2
)
Array
(
    [ID] => 3318
    [title] => Our Team
    [key] => 3
    [x_key] => 1
    [depth] => 
    [menu_item_parent] => 3317
    [has_widget_class] => 0
    [is_depth_1_first_child] => 0
    [x_has_dropdown] => 1
    [has_break_class] => 0
    [x_cols_increase] => 0
    [x_cols] => 2
)
Array
(
    [ID] => 3319
    [title] => Leadership Team
    [key] => 4
    [x_key] => 1
    [depth] => 1
    [menu_item_parent] => 3316
    [has_widget_class] => 0
    [is_depth_1_first_child] => 0
    [x_has_dropdown] => 1
    [has_break_class] => 0
    [x_cols_increase] => 0
    [x_cols] => 2
)
Array
(
    [ID] => 3320
    [title] => Professional Affiliations
    [key] => 5
    [x_key] => 1
    [depth] => 1
    [menu_item_parent] => 3316
    [has_widget_class] => 0
    [is_depth_1_first_child] => 0
    [x_has_dropdown] => 1
    [has_break_class] => 0
    [x_cols_increase] => 0
    [x_cols] => 2
)
Array
(
    [ID] => 3321
    [title] => Clients
    [key] => 6
    [x_key] => 1
    [depth] => 1
    [menu_item_parent] => 3316
    [has_widget_class] => 0
    [is_depth_1_first_child] => 0
    [x_has_dropdown] => 1
    [has_break_class] => 0
    [x_cols_increase] => 1
    [x_has_break_class] => 1
    [x_cols] => 3
)
Array
(
    [ID] => 3322
    [title] => Partnerships
    [key] => 7
    [x_key] => 1
    [depth] => 1
    [menu_item_parent] => 3316
    [has_widget_class] => 0
    [is_depth_1_first_child] => 0
    [x_has_dropdown] => 1
    [has_break_class] => 0
    [x_cols_increase] => 0
    [x_cols] => 3
)
 -->

Si desea verificar si el objeto walker es de la megaMenuWalkerclase, puede usar:

if( ! is_object( $args->walker ) || ! is_a( $args->walker, 'megaMenuWalker' ) )
    return $items;

en lugar de

if( 'primary' !== $args->theme_location ) 
            return $items;

Espero que esto ayude.

Birgire
fuente
Curiosamente, esto genera 1 como el número. Vea hfracquetandfitness.myweblinx.net (inspeccione el primer li en la navegación superior). EDITAR - Creo que sé por qué. La ruptura se agrega a <li>elementos secundarios directos , no a los actuales <li>. Debe comprobar que es un hijo directo, y no el primer hijo que también tiene la clase de break.
JacobTheDev
Actualicé la respuesta, tenía un error tipográfico (&& en lugar de ||).
Birgire
Ah, eso ayudó un poco, pero veo que está contando si el elemento de la lista actual tiene la clase de break. En realidad, debe contar si los niños directos tienen la clase de break, no en sí mismos, y excluir al primer niño lien el niño ul, si eso tiene sentido.
JacobTheDev
Supongo que los <li>elementos raíz están en profundidad 0, y luego cuento las clases de ruptura / widget de los <li>elementos secundarios en profundidad 1. Creo que debería comenzar $x_colsen at en 1lugar de 0, ya que los nsaltos dan n+1columnas.
Birgire
1
De nada. Actualicé la respuesta con un ejemplo de cómo puede verificar la clase de caminante en lugar de la ubicación del tema. Mucha suerte con tu proyecto.
Birgire