Affiche une portion / branche de l'arborescence de menu à l'aide de wp_nav_menu ()

107

J'ai un menu défini dans WP Admin qui ressemble à ceci:

Jeveuxpouvoiraffichertouslesliensenfantsdanslabarrelatéralechaquefoisquejesuissurunepageparent.Parexemple,sil’utilisateursetrouvesurmapage"À propos de nous", je souhaite qu’une liste des 4 liens surlignés en vert apparaisse dans la barre latérale.

J'ai consulté la documentation de wp_nav_menu () et il ne semble pas y avoir de moyen intégré pour spécifier un nœud particulier d'un menu donné à utiliser comme point de départ lors de la génération des liens.

J'ai créé une solution pour une situation similaire qui s’appuie sur les relations créées par la page parent, mais je recherche une relation utilisant spécifiquement le système de menus. Toute aide serait la bienvenue.

    
posée jessegavin 11.10.2010 - 23:50
la source

10 réponses

72

C’était toujours dans mes pensées alors je l’ai revisité et mis en place cette solution qui ne dépend pas beaucoup du contexte:

add_filter( 'wp_nav_menu_objects', 'submenu_limit', 10, 2 );

function submenu_limit( $items, $args ) {

    if ( empty( $args->submenu ) ) {
        return $items;
    }

    $ids       = wp_filter_object_list( $items, array( 'title' => $args->submenu ), 'and', 'ID' );
    $parent_id = array_pop( $ids );
    $children  = submenu_get_children_ids( $parent_id, $items );

    foreach ( $items as $key => $item ) {

        if ( ! in_array( $item->ID, $children ) ) {
            unset( $items[$key] );
        }
    }

    return $items;
}

function submenu_get_children_ids( $id, $items ) {

    $ids = wp_filter_object_list( $items, array( 'menu_item_parent' => $id ), 'and', 'ID' );

    foreach ( $ids as $id ) {

        $ids = array_merge( $ids, submenu_get_children_ids( $id, $items ) );
    }

    return $ids;
}

Utilisation

$args = array(
    'theme_location' => 'slug-of-the-menu', // the one used on register_nav_menus
    'submenu' => 'About Us', // could be used __() for translations
);

wp_nav_menu( $args );
    
réponse donnée Rarst 12.10.2010 - 11:00
la source
14

@goldenapples: Votre classe Walker ne fonctionne pas. Mais l'idée est vraiment bonne. J'ai créé un walker basé sur votre idée:

class Selective_Walker extends Walker_Nav_Menu
{
    function walk( $elements, $max_depth) {

        $args = array_slice(func_get_args(), 2);
        $output = '';

        if ($max_depth < -1) //invalid parameter
            return $output;

        if (empty($elements)) //nothing to walk
            return $output;

        $id_field = $this->db_fields['id'];
        $parent_field = $this->db_fields['parent'];

        // flat display
        if ( -1 == $max_depth ) {
            $empty_array = array();
            foreach ( $elements as $e )
                $this->display_element( $e, $empty_array, 1, 0, $args, $output );
            return $output;
        }

        /*
         * need to display in hierarchical order
         * separate elements into two buckets: top level and children elements
         * children_elements is two dimensional array, eg.
         * children_elements[10][] contains all sub-elements whose parent is 10.
         */
        $top_level_elements = array();
        $children_elements  = array();
        foreach ( $elements as $e) {
            if ( 0 == $e->$parent_field )
                $top_level_elements[] = $e;
            else
                $children_elements[ $e->$parent_field ][] = $e;
        }

        /*
         * when none of the elements is top level
         * assume the first one must be root of the sub elements
         */
        if ( empty($top_level_elements) ) {

            $first = array_slice( $elements, 0, 1 );
            $root = $first[0];

            $top_level_elements = array();
            $children_elements  = array();
            foreach ( $elements as $e) {
                if ( $root->$parent_field == $e->$parent_field )
                    $top_level_elements[] = $e;
                else
                    $children_elements[ $e->$parent_field ][] = $e;
            }
        }

        $current_element_markers = array( 'current-menu-item', 'current-menu-parent', 'current-menu-ancestor' );  //added by continent7
        foreach ( $top_level_elements as $e ){  //changed by continent7
            // descend only on current tree
            $descend_test = array_intersect( $current_element_markers, $e->classes );
            if ( !empty( $descend_test ) ) 
                $this->display_element( $e, $children_elements, 2, 0, $args, $output );
        }

        /*
         * if we are displaying all levels, and remaining children_elements is not empty,
         * then we got orphans, which should be displayed regardless
         */
         /* removed by continent7
        if ( ( $max_depth == 0 ) && count( $children_elements ) > 0 ) {
            $empty_array = array();
            foreach ( $children_elements as $orphans )
                foreach( $orphans as $op )
                    $this->display_element( $op, $empty_array, 1, 0, $args, $output );
         }
        */
         return $output;
    }
}

Vous pouvez maintenant utiliser:

<?php wp_nav_menu( 
   array(
       'theme_location'=>'test', 
       'walker'=>new Selective_Walker() ) 
   ); ?>

La sortie est une liste contenant l’élément racine actuel et ses enfants (pas leurs enfants). Def: Elément racine: = L'élément de menu de niveau supérieur qui correspond à la page en cours ou est le parent d'une page en cours ou le parent d'un parent ...

Cela ne répond pas exactement à la question initiale, mais presque, car il existe toujours l'élément de niveau supérieur. Cela me convient, car je souhaite que l’élément de niveau supérieur figure dans le titre de la barre latérale. Si vous souhaitez vous en débarrasser, vous devrez peut-être remplacer display_element ou utiliser un analyseur HTML.

    
réponse donnée davidn 07.02.2011 - 15:15
la source
12

Bonjour @jessegavin :

Les menus de navigation sont stockés dans une combinaison de types de publication personnalisés et de taxonomies personnalisées. Chaque menu est stocké sous la forme d'un terme ( "À propos du menu" , trouvé dans wp_terms ) d'une taxonomie personnalisée (c'est-à-dire nav_menu , trouvé dans wp_term_taxonomy .)

Chaque élément du menu de navigation est stocké sous forme de publication de post_type=='nav_menu_item' (c.-à-d. "À propos de la société" , présent dans wp_posts ) avec ses attributs stockés sous la forme de méta (dans wp_postmeta ). en utilisant un préfixe meta_key de _menu_item_*_menu_item_menu_item_parent est l'ID de l'élément de menu de navigation du parent de votre élément de menu, post.

La relation entre les menus et les éléments de menu est stockée dans wp_term_relationships , où object_id correspond au $post->ID pour l'élément de menu de navigation et le $term_relationships->term_taxonomy_id correspond au menu défini collectivement dans wp_term_taxonomy et wp_terms . .

Je suis presque sûr qu'il serait possible de raccorder 'wp_update_nav_menu' et 'wp_update_nav_menu_item' pour créer des menus dans wp_terms et un ensemble parallèle de relations dans wp_term_taxonomy et wp_term_relationships où chaque élément de menu de navigation comportant des éléments de sous-menu de navigation devient également son propre menu de navigation.

Vous souhaitez également raccorder 'wp_get_nav_menus' (que j'ai suggéré d'ajouter à WP 3.0 sur la base d'un travail similaire que je faisais il y a quelques mois) à Assurez-vous que les menus de navigation générés ne s'affichent pas pour que l'utilisateur puisse les manipuler, sinon ils se désynchroniseraient très vite et vous auriez un cauchemar de données sur votre main.

Cela semble être un projet amusant et utile, mais c’est un peu plus de code et de tests que ce que je peux me permettre de faire maintenant, en partie parce que tout ce qui synchronise les données a tendance à être un PITA quand il s’agit de résoudre tous les bugs. (et parce que les clients payants me poussent à faire avancer les choses.) Mais, fort de ces informations, je suis un développeur de plug-in WordPress motivé qui pourrait le coder s’il le souhaitait.

Bien sûr, vous réalisez maintenant que si vous le codez, vous êtes obligé de le poster ici pour que nous puissions tous profiter de vos largesses! : -)

    
réponse donnée MikeSchinkel 12.10.2010 - 09:50
la source
10

Ceci est une extension du marcheur qui devrait faire ce que vous cherchez:

class Selective_Walker extends Walker_Nav_Menu
{

    function walk( $elements, $max_depth) {

        $args = array_slice(func_get_args(), 2);
        $output = '';

        if ($max_depth < -1) //invalid parameter
            return $output;

        if (empty($elements)) //nothing to walk
            return $output;

        $id_field = $this->db_fields['id'];
        $parent_field = $this->db_fields['parent'];

        // flat display
        if ( -1 == $max_depth ) {
            $empty_array = array();
            foreach ( $elements as $e )
                $this->display_element( $e, $empty_array, 1, 0, $args, $output );
            return $output;
        }

        /*
         * need to display in hierarchical order
         * separate elements into two buckets: top level and children elements
         * children_elements is two dimensional array, eg.
         * children_elements[10][] contains all sub-elements whose parent is 10.
         */
        $top_level_elements = array();
        $children_elements  = array();
        foreach ( $elements as $e) {
            if ( 0 == $e->$parent_field )
                $top_level_elements[] = $e;
            else
                $children_elements[ $e->$parent_field ][] = $e;
        }

        /*
         * when none of the elements is top level
         * assume the first one must be root of the sub elements
         */
        if ( empty($top_level_elements) ) {

            $first = array_slice( $elements, 0, 1 );
            $root = $first[0];

            $top_level_elements = array();
            $children_elements  = array();
            foreach ( $elements as $e) {
                if ( $root->$parent_field == $e->$parent_field )
                    $top_level_elements[] = $e;
                else
                    $children_elements[ $e->$parent_field ][] = $e;
            }
        }

        $current_element_markers = array( 'current-menu-item', 'current-menu-parent', 'current-menu-ancestor' );

        foreach ( $top_level_elements as $e ) {

            // descend only on current tree
            $descend_test = array_intersect( $current_element_markers, $e->classes );
            if ( empty( $descend_test ) )  unset ( $children_elements );

            $this->display_element( $e, $children_elements, $max_depth, 0, $args, $output );
        }

        /*
         * if we are displaying all levels, and remaining children_elements is not empty,
         * then we got orphans, which should be displayed regardless
         */
        if ( ( $max_depth == 0 ) && count( $children_elements ) > 0 ) {
            $empty_array = array();
            foreach ( $children_elements as $orphans )
                foreach( $orphans as $op )
                    $this->display_element( $op, $empty_array, 1, 0, $args, $output );
         }

         return $output;
    }

}

Basé vaguement sur le code de mfields que j'ai mentionné dans mon commentaire plus tôt. Il ne fait que vérifier, lorsque vous parcourez le menu, si l'élément actuel est (1) l'élément de menu actuel ou (2) un ancêtre de l'élément de menu actuel, et ne développe la sous-arborescence située en dessous que si l'une de ces conditions est vraie. . J'espère que cela fonctionne pour vous.

Pour l'utiliser, ajoutez simplement un argument "walker" lorsque vous appelez le menu, par exemple:

<?php wp_nav_menu( 
   array(
       'theme_location'=>'test', 
       'walker'=>new Selective_Walker() ) 
   ); ?>
    
réponse donnée goldenapples 15.10.2010 - 22:06
la source
8

Mise à jour: je l'ai transformé en plugin. Téléchargez-le ici .

Je devais résoudre ce problème moi-même et finalement finir par écrire un filtre sur les résultats de la recherche de menu. Il vous permet d’utiliser wp_nav_menu normalement, mais de choisir une sous-section du menu en fonction du titre de l’élément parent. Ajoutez un paramètre submenu au menu comme suit:

wp_nav_menu(array(
  'menu' => 'header',
  'submenu' => 'About Us',
));

Vous pouvez même accéder à plusieurs niveaux en insérant des barres obliques dans:

wp_nav_menu(array(
  'menu' => 'header',
  'submenu' => 'About Us/Board of Directors'
));

Ou si vous préférez avec un tableau:

wp_nav_menu(array(
  'menu' => 'header',
  'submenu' => array('About Us', 'Board of Directors')
));

Il utilise une version slug du titre, ce qui devrait lui permettre de pardonner des choses comme les majuscules et la ponctuation.

    
réponse donnée Marcus Downing 21.04.2011 - 13:41
la source
8

J'ai mis sur pied le cours suivant pour moi-même. Il trouvera le parent de navigation supérieur de la page actuelle ou vous pourrez lui attribuer un ID de navigation cible dans le constructeur du lecteur.

class Walker_SubNav_Menu extends Walker_Nav_Menu {
    var $target_id = false;

    function __construct($target_id = false) {
        $this->target_id = $target_id;
    }

    function walk($items, $depth) {
        $args = array_slice(func_get_args(), 2);
        $args = $args[0];
        $parent_field = $this->db_fields['parent'];
        $target_id = $this->target_id;
        $filtered_items = array();

        // if the parent is not set, set it based on the post
        if (!$target_id) {
            global $post;
            foreach ($items as $item) {
                if ($item->object_id == $post->ID) {
                    $target_id = $item->ID;
                }
            }
        }

        // if there isn't a parent, do a regular menu
        if (!$target_id) return parent::walk($items, $depth, $args);

        // get the top nav item
        $target_id = $this->top_level_id($items, $target_id);

        // only include items under the parent
        foreach ($items as $item) {
            if (!$item->$parent_field) continue;

            $item_id = $this->top_level_id($items, $item->ID);

            if ($item_id == $target_id) {
                $filtered_items[] = $item;
            }
        }

        return parent::walk($filtered_items, $depth, $args);
    }

    // gets the top level ID for an item ID
    function top_level_id($items, $item_id) {
        $parent_field = $this->db_fields['parent'];

        $parents = array();
        foreach ($items as $item) {
            if ($item->$parent_field) {
                $parents[$item->ID] = $item->$parent_field;
            }
        }

        // find the top level item
        while (array_key_exists($item_id, $parents)) {
            $item_id = $parents[$item_id];
        }

        return $item_id;
    }
}

Appel de navigation:

wp_nav_menu(array(
    'theme_location' => 'main_menu',
    'walker' => new Walker_SubNav_Menu(22), // with ID
));
    
réponse donnée Matt 01.02.2012 - 00:19
la source
4

@davidn @hakre Bonjour, j’ai une solution laide sans HTML-Parser ni display_element.

 class Selective_Walker extends Walker_Nav_Menu
    {
        function walk( $elements, $max_depth) {

            $args = array_slice(func_get_args(), 2);
            $output = '';

            if ($max_depth < -1) //invalid parameter
                return $output;

            if (empty($elements)) //nothing to walk
                return $output;

            $id_field = $this->db_fields['id'];
            $parent_field = $this->db_fields['parent'];

            // flat display
            if ( -1 == $max_depth ) {
                $empty_array = array();
                foreach ( $elements as $e )
                    $this->display_element( $e, $empty_array, 1, 0, $args, $output );
                return $output;
            }

            /*
             * need to display in hierarchical order
             * separate elements into two buckets: top level and children elements
             * children_elements is two dimensional array, eg.
             * children_elements[10][] contains all sub-elements whose parent is 10.
             */
            $top_level_elements = array();
            $children_elements  = array();
            foreach ( $elements as $e) {
                if ( 0 == $e->$parent_field )
                    $top_level_elements[] = $e;
                else
                    $children_elements[ $e->$parent_field ][] = $e;
            }

            /*
             * when none of the elements is top level
             * assume the first one must be root of the sub elements
             */
            if ( empty($top_level_elements) ) {

                $first = array_slice( $elements, 0, 1 );
                $root = $first[0];

                $top_level_elements = array();
                $children_elements  = array();
                foreach ( $elements as $e) {
                    if ( $root->$parent_field == $e->$parent_field )
                        $top_level_elements[] = $e;
                    else
                        $children_elements[ $e->$parent_field ][] = $e;
                }
            }

            $current_element_markers = array( 'current-menu-item', 'current-menu-parent', 'current-menu-ancestor' );  //added by continent7
            foreach ( $top_level_elements as $e ){  //changed by continent7
                // descend only on current tree
                $descend_test = array_intersect( $current_element_markers, $e->classes );
                if ( !empty( $descend_test ) ) 
                    $this->display_element( $e, $children_elements, 2, 0, $args, $output );
            }

            /*
             * if we are displaying all levels, and remaining children_elements is not empty,
             * then we got orphans, which should be displayed regardless
             */
             /* removed by continent7
            if ( ( $max_depth == 0 ) && count( $children_elements ) > 0 ) {
                $empty_array = array();
                foreach ( $children_elements as $orphans )
                    foreach( $orphans as $op )
                        $this->display_element( $op, $empty_array, 1, 0, $args, $output );
             }
            */

/*added by alpguneysel  */
                $pos = strpos($output, '<a');
            $pos2 = strpos($output, 'a>');
            $topper= substr($output, 0, $pos).substr($output, $pos2+2);
            $pos3 = strpos($topper, '>');
            $lasst=substr($topper, $pos3+1);
            $submenu= substr($lasst, 0, -6);

        return $submenu;
        }
    }
    
réponse donnée Alp Güneysel 15.03.2011 - 19:07
la source
3

La sortie du menu de navigation inclut de nombreuses classes pour l’élément actuel, l’ancêtre de l’élément actuel, etc. Dans certaines situations, j’ai été en mesure de faire ce que vous voulez en laissant toute la sortie de l’arbre de navigation, puis en utilisant css pour créer il ne concerne que les enfants de la page en cours, etc.

    
réponse donnée user3017 08.02.2011 - 22:39
la source
3

J'ai fabriqué un marcheur modifié qui devrait aider! Pas parfait - il laisse quelques éléments vides, mais ça fait l'affaire. La modification concerne essentiellement les bits $ current_branch. J'espère que ça aide quelqu'un!

class Kanec_Walker_Nav_Menu extends Walker {
/**
 * @see Walker::$tree_type
 * @since 3.0.0
 * @var string
 */
var $tree_type = array( 'post_type', 'taxonomy', 'custom' );

/**
 * @see Walker::$db_fields
 * @since 3.0.0
 * @todo Decouple this.
 * @var array
 */
var $db_fields = array( 'parent' => 'menu_item_parent', 'id' => 'db_id' );

/**
 * @see Walker::start_lvl()
 * @since 3.0.0
 *
 * @param string $output Passed by reference. Used to append additional content.
 * @param int $depth Depth of page. Used for padding.
 */
function start_lvl(&$output, $depth) {
    $indent = str_repeat("\t", $depth);
    $output .= "\n$indent<ul class=\"sub-menu\">\n";
}

/**
 * @see Walker::end_lvl()
 * @since 3.0.0
 *
 * @param string $output Passed by reference. Used to append additional content.
 * @param int $depth Depth of page. Used for padding.
 */
function end_lvl(&$output, $depth) {
    global $current_branch;
    if ($depth == 0) $current_branch = false;
    $indent = str_repeat("\t", $depth);
    $output .= "$indent</ul>\n";
}

/**
 * @see Walker::start_el()
 * @since 3.0.0
 *
 * @param string $output Passed by reference. Used to append additional content.
 * @param object $item Menu item data object.
 * @param int $depth Depth of menu item. Used for padding.
 * @param int $current_page Menu item ID.
 * @param object $args
 */
function start_el(&$output, $item, $depth, $args) {
    global $wp_query;
    global $current_branch;

    // Is this menu item in the current branch?
    if(in_array('current-menu-ancestor',$item->classes) ||
    in_array('current-menu-parent',$item->classes) ||
    in_array('current-menu-item',$item->classes)) {
        $current_branch = true; 
    }

    if($current_branch && $depth > 0) {
        $indent = ( $depth ) ? str_repeat( "\t", $depth ) : '';

        $class_names = $value = '';

        $classes = empty( $item->classes ) ? array() : (array) $item->classes;
        $classes[] = 'menu-item-' . $item->ID;

        $class_names = join( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item ) );
        $class_names = ' class="' . esc_attr( $class_names ) . '"';

        $id = apply_filters( 'nav_menu_item_id', 'menu-item-'. $item->ID, $item, $args );
        $id = strlen( $id ) ? ' id="' . esc_attr( $id ) . '"' : '';

        $output .= $indent . '<li' . $id . $value . $class_names .'>';

        $attributes  = ! empty( $item->attr_title ) ? ' title="'  . esc_attr( $item->attr_title ) .'"' : '';
        $attributes .= ! empty( $item->target )     ? ' target="' . esc_attr( $item->target     ) .'"' : '';
        $attributes .= ! empty( $item->xfn )        ? ' rel="'    . esc_attr( $item->xfn        ) .'"' : '';
        $attributes .= ! empty( $item->url )        ? ' href="'   . esc_attr( $item->url        ) .'"' : '';

        $item_output = $args->before;
        $item_output .= '<a'. $attributes .'>';
        $item_output .= $args->link_before . apply_filters( 'the_title', $item->title, $item->ID ) . $args->link_after;
        $item_output .= '</a>';
        $item_output .= $args->after;

        $output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
    }

}

/**
 * @see Walker::end_el()
 * @since 3.0.0
 *
 * @param string $output Passed by reference. Used to append additional content.
 * @param object $item Page data object. Not used.
 * @param int $depth Depth of page. Not Used.
 */
function end_el(&$output, $item, $depth) {
    global $current_branch;
    if($current_branch && $depth > 0) $output .= "</li>\n";
    if($depth == 0) $current_branch = 0;
}

}

    
réponse donnée user2735 20.04.2011 - 20:46
la source
3

Découvrez le code dans mon plugin ou utilisez-le à vos fins;)

Ce plugin ajoute un widget "Menu de navigation" amélioré. Il offre de nombreuses options qui peuvent être définies pour personnaliser la sortie du menu personnalisé via le widget.

Les fonctionnalités incluent:

  • Hiérarchie personnalisée - "Seuls les sous-éléments liés" ou "Uniquement en relation sous-éléments ".
  • Profondeur de départ et niveau maximal d'affichage + affichage à plat.
  • Affiche tous les éléments de menu commençant par celui sélectionné.
  • Affiche uniquement le chemin direct vers l'élément actuel ou uniquement les enfants de
    élément sélectionné (possibilité d'inclure l'élément parent).
  • Classe personnalisée pour un bloc de widgets.
  • Et presque tous les paramètres de la fonction wp_nav_menu.

enlace

    
réponse donnée Ján Bočínec 10.01.2012 - 01:59
la source

Lire d'autres questions sur les étiquettes