Divida wp_nav_menu com walker personalizado

Estou tentando criar um menu que mostra um máximo de 5 itens. Se houver mais itens, ele deve envolvê-los em outro elemento

    para criar um menu suspenso.

    5 Itens ou menos:

    Suspenso

    6 Itens ou mais

    Suspenso

    Eu sei que esse tipo de funcionalidade pode ser facilmente criada com um walker que conta os itens do menu e envolve se houver mais de 5 o remaing em um

      separado. Mas eu não sei como criar este walker.

      O código que mostra o meu menu no momento é o seguinte:

        'navigation', 'fallback_cb' => 'custom_menu', 'walker' =>new Custom_Walker_Nav_Menu ) ); ?> 

      Percebi que se o menu não for definido pelo usuário e ele usa a function fallback, o walker não tem efeito. Preciso que funcione em ambos os casos.

      Solutions Collecting From Web of "Divida wp_nav_menu com walker personalizado"

      Usando um Walker personalizado, o método start_el() tem access a $depth param: quando ele é 0 o elemnt é um top, e podemos usar essa informação para manter um contador interno.

      Quando o contador atinge um limite, podemos usar o DOMDocument para obter o resultado HTML completo, apenas o último elemento adicionado, envolvê-lo em um submenu e adicioná-lo novamente ao HTML.


      Editar

      Quando o número de elementos é exatamente o número que precisamos de + 1, por exemplo, exigimos que 5 elementos sejam visíveis eo menu tenha 6, não faz sentido dividir o menu, pois os elementos serão de qualquer maneira. O código foi editado para resolver isso.


      Aqui está o código:

       class SplitMenuWalker extends Walker_Nav_Menu { private $split_at; private $button; private $count = 0; private $wrappedOutput; private $replaceTarget; private $wrapped = false; private $toSplit = false; public function __construct($split_at = 5, $button = '') { $this->split_at = $split_at; $this->button = $button; } public function walk($elements, $max_depth) { $args = array_slice(func_get_args(), 2); $output = parent::walk($elements, $max_depth, reset($args)); return $this->toSplit ? $output.'' : $output; } public function start_el(&$output, $item, $depth = 0, $args = array(), $id = 0 ) { $this->count += $depth === 0 ? 1 : 0; parent::start_el($output, $item, $depth, $args, $id); if (($this->count === $this->split_at) && ! $this->wrapped) { // split at number has been reached generate and store wrapped output $this->wrapped = true; $this->replaceTarget = $output; $this->wrappedOutput = $this->wrappedOutput($output); } elseif(($this->count === $this->split_at + 1) && ! $this->toSplit) { // split at number has been exceeded, replace regular with wrapped output $this->toSplit = true; $output = str_replace($this->replaceTarget, $this->wrappedOutput, $output); } } private function wrappedOutput($output) { $dom = new DOMDocument; $dom->loadHTML($output.''); $lis = $dom->getElementsByTagName('li'); $last = trim(substr($dom->saveHTML($lis->item($lis->length-1)), 0, -5)); // remove last li $wrappedOutput = substr(trim($output), 0, -1 * strlen($last)); $classs = array( 'menu-item', 'menu-item-type-custom', 'menu-item-object-custom', 'menu-item-has-children', 'menu-item-split-wrapper' ); // add wrap li element $wrappedOutput .= '
    • '; // add the "more" link $wrappedOutput .= $this->button; // add the last item wrapped in a submenu and return return $wrappedOutput . '
    • O uso é bastante simples:

       // by default make visible 5 elements wp_nav_menu(array('menu' => 'my_menu', 'walker' => new SplitMenuWalker())); // let's make visible 2 elements wp_nav_menu(array('menu' => 'another_menu', 'walker' => new SplitMenuWalker(2))); // customize the link to click/over to see wrapped items wp_nav_menu(array( 'menu' => 'another_menu', 'walker' => new SplitMenuWalker(5, 'more...') )); 

      Existe mesmo uma maneira de tornar isso possível com CSS sozinho. Isso tem algumas limitações, mas eu ainda pensei que poderia ser uma abordagem interessante:

      Limitações

      • Você precisa codificar a largura do menu suspenso
      • Navegador-Suporte. Você basicamente precisa de seletores CSS3 . Mas tudo do IE8 deve funcionar, embora não tenha testado isso.
      • Isso é mais uma prova de conceito. Existem várias desvantagens, como funciona apenas se não houver sub-itens.

      Abordagem

      Embora eu não esteja realmente usando “Consultas de quantidade”, o uso criativo de :nth-child e ~ eu li nas consultas de quantidade recentes para CSS foram o que me levou a esta solução.

      A abordagem é basicamente esta:

      1. Ocultar todos os itens após o 4º
      2. Adicione ... pontos usando um pseudo-elemento anterior.
      3. Ao pairar os pontos (ou qualquer um dos elementos ocultos), mostre os itens extras como o submenu.

      Aqui está o código CSS para uma marcação padrão do menu WordPress. Eu comentei em linha.

       /* Optional: Center the navigation */ .main-navigation { text-align: center; } .menu-main-menu-container { display: inline-block; } /* Float menu items */ .nav-menu li { float:left; list-style-type: none; } /* Pull the 5th menu item to the left a bit so that there isn't too much space between item 4 and ... */ .nav-menu li:nth-child(4) { margin-right: -60px; } /* Create a pseudo element for ... and force line break afterwards (Hint: Use a symbol font to improve styling) */ .nav-menu li:nth-child(5):before { content: "...\A"; white-space: pre; } /* Give the first 4 items some padding and push them in front of the submenu */ .nav-menu li:nth-child(-n+4) { padding-right: 15px; position: relative; z-index: 1; } /* Float dropdown-items to the right. Hardcode width of dropdown. */ .nav-menu li:nth-child(n+5) { float:right; clear: right; width: 150px; } /* Float Links in dropdown to the right and hide by default */ .nav-menu li:nth-child(n+5) a{ display: none; float: right; clear: right; } /* When hovering the menu, show all menu items from the 5th on */ .nav-menu:hover li:nth-child(n+5) a, .nav-menu:hover li:nth-child(n+5) ~ li a{ display: inherit; } /* When hovering one of the first 4 items, hide all items after it so we do not activate the dropdown on the first 4 items */ .nav-menu li:nth-child(-n+4):hover ~ li:nth-child(n+5) a{ display: none; } 

      Eu também criei um jsfiddle para mostrar isso em ação: http://jsfiddle.net/jg6pLfd1/

      Se você tiver alguma dúvida sobre como isso funciona, deixe um comentário, eu ficaria feliz em esclarecer o código ainda mais.

      Você pode usar o filtro wp_nav_menu_items . Ele aceita a saída do menu e os argumentos que possuem atributos do menu, como menu slug, container, etc.

       add_filter('wp_nav_menu_items', 'wpse_180221_nav_menu_items', 20, 2); function wpse_180221_nav_menu_items($items, $args) { if ($args->menu != 'my-menu-slug') { return $items; } // extract all 
    • elements from menu output preg_match_all('/
    • ]*>.*?< \/li>/iU', $items, $matches); // if menu has less the 5 items, just do nothing if (! isset($matches[0][5])) { return $items; } // add
        after 5th item (can be any number - can use eg site-wide variable) $matches[0][5] = ''; }
    • Tem uma function de trabalho, mas não tenho certeza se é a melhor solução.

      Eu usei um walker personalizado:

       class Custom_Walker_Nav_Menu extends Walker_Nav_Menu { function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) { global $wp_query; $indent = ( $depth ) ? str_repeat( "\t", $depth ) : ''; $classs = empty( $item->classs ) ? array() : (array) $item->classs; $classs[] = 'menu-item-' . $item->ID; $class_names = join( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classs ), $item, $args, $depth ) ); $class_names = $class_names ? ' class="' . esc_attr( $class_names ) . '"' : ''; $id = apply_filters( 'nav_menu_item_id', 'menu-item-'. $item->ID, $item, $args, $depth ); $id = $id ? ' id="' . esc_attr( $id ) . '"' : ''; /** * This counts the $menu_items and wraps if there are more then 5 items the * remaining items into an extra 
        */ global $menu_items; $menu_items = substr_count($output,'
      • '; $atts = array(); $atts['title'] = ! empty( $item->attr_title ) ? $item->attr_title : ''; $atts['target'] = ! empty( $item->target ) ? $item->target : ''; $atts['rel'] = ! empty( $item->xfn ) ? $item->xfn : ''; $atts['href'] = ! empty( $item->url ) ? $item->url : ''; $atts = apply_filters( 'nav_menu_link_attributes', $atts, $item, $args, $depth ); $attributes = ''; foreach ( $atts as $attr => $value ) { if ( ! empty( $value ) ) { $value = ( 'href' === $attr ) ? esc_url( $value ) : esc_attr( $value ); $attributes .= ' ' . $attr . '="' . $value . '"'; } } $item_output = $args->before; $item_output .= ''; $item_output .= $args->link_before . apply_filters( 'the_title', $item->title, $item->ID ) . $args->link_after; $item_output .= ''; $item_output .= $args->after; $output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args ); } }

    A function que mostra o menu atual é a seguinte:

      < ?php wp_nav_menu( array( 'container' => false, 'theme_location' => 'navigation', 'fallback_cb' => 'custom_menu', 'walker' =>new Custom_Walker_Nav_Menu ) ); global $menu_items; // This adds the closing  and  if there are more then 4 items in the menu if ($menu_items > 4) { echo ""; } ?> 

    Eu declarei a variável global $ menu_items e usei para mostrar as tags

  • e

      fechamento. Provavelmente é possível fazer isso também dentro do walker personalizado, mas não encontrei onde e como.

      Dois problemas: 1. Se houver apenas 5 itens no menu, ele envolve o último item, bem como em um argumento, não há necessidade disso.

      1. Simplesmente funciona se o usuário realmente alocou um menu para a localização do tema, o walker não triggers se wp_nav_menu estiver mostrando a function de retorno