커스텀 워커로 wp_nav_menu 분할


16

최대 5 개의 항목을 표시하는 메뉴를 만들려고합니다. 항목이 더 있으면 다른 <ul>요소 로 감싸서 드롭 다운을 만들어야합니다.

5 항목 이하

쓰러지 다

6 항목 이상

쓰러지 다

메뉴 항목을 세고 나머지에 5 개 이상이 있으면 랩핑하는 워커로 이러한 종류의 기능을 쉽게 만들 수 있습니다 <ul>. 그러나 나는이 워커를 만드는 방법을 모른다.

현재 내 메뉴를 표시하는 코드는 다음과 같습니다.

<?php wp_nav_menu( array( 'theme_location' => 'navigation', 'fallback_cb' => 'custom_menu', 'walker' =>new Custom_Walker_Nav_Menu ) ); ?>

메뉴가 사용자가 정의하지 않고 대신 폴백 기능을 사용하면 워커가 영향을 미치지 않습니다. 두 경우 모두 작동해야합니다.


1
사용자 정의 메뉴 워커는 확장하는 클래스입니다 Walker_Nav_Menu및 분과의 예는있다 . "Walker를 만드는 방법을 모르겠습니다"는 무슨 뜻입니까?
— cybmeta

아이디어가 실제로 굉장하기 때문에 Btw, +1. 어떻게 넘어 졌습니까? 소스 게시물이 있습니까? 그렇다면 기꺼이 읽어 드리겠습니다. 미리 감사드립니다.
— kaiser

@kaiser 그냥 불쾌한 디자인 아이디어 :) 소스 게시물이 없으므로 그 이유를 묻습니다.
— Snowball

@cybmeta 워커를 만들고 코덱에 예제가 있다는 것을 알고 있지만이 특정 문제에 대한 예제는 없습니다. 그래서 나에게 해결책을주는 커스텀 워커를 만드는 법을 모른다
— Snowball

UX.SE 담당자 에게이 아이디어에 대해 문의하고 사용자에게 문제가 있는지 확인해야합니다. UX는 유용성 / 경험에 대한 실질적인 확인을 제공하고 정기적으로 잘 생각되는 답변과 문제를 제공하는 정말 멋진 사이트입니다. 당신은 다시 돌아올 수 있고 우리 모두 그 아이디어를 다듬습니다. (정말 멋질 것입니다!).
— 카이저

답변:


9

사용자 정의 Walker를 사용하면 start_el()메소드가 $depthparam에 액세스 할 수 있습니다 0. elemnt가 최상위 인 경우이 정보를 사용하여 내부 카운터를 유지할 수 있습니다.

카운터가 한도에 도달 DOMDocument하면 마지막으로 추가 한 요소 만 전체 HTML 출력에서 ​​가져 와서 하위 메뉴에 싸서 HTML에 다시 추가하는 데 사용할 수 있습니다.


편집하다

요소의 수가 정확히 + 1 필요한 숫자 + 예를 들어 5 개의 요소가 표시되어야하고 메뉴에 6이있는 경우 요소는 6 가지 방법이므로 메뉴를 분할하는 것은 의미가 없습니다. 이를 해결하기 위해 코드가 편집되었습니다.


코드는 다음과 같습니다.

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 = '<a href="#">&hellip;</a>') {
      $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.'</ul></li>' : $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.'</li>');
       $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));
       $classes = array(
         'menu-item',
         'menu-item-type-custom',
         'menu-item-object-custom',
         'menu-item-has-children',
         'menu-item-split-wrapper'
       );
       // add wrap li element
       $wrappedOutput .= '<li class="'.implode(' ', $classes).'">';
       // add the "more" link
       $wrappedOutput .= $this->button;
       // add the last item wrapped in a submenu and return
       return $wrappedOutput . '<ul class="sub-menu">'. $last;
   }
}

사용법은 매우 간단합니다.

// 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, '<a href="#">more...</a>')
));

훌륭하게 작동합니다! 멋진 작품 주세페. 이것의 가장 큰 장점은 처음 5 개의 메뉴 요소에 하위 메뉴가있는 경우에도 잘 작동한다는 것입니다. 그리고 필요하지 않은 경우 단일 메뉴 포인트 만 하위 메뉴로 감싸지 않습니다. 사소한 일 : 기본적으로 6 개의 요소를 표시 $split_at = 5하지만 $count색인은 0에서 시작합니다.
— Snowball

@ Snownow 감사합니다. 사소한 문제를 해결했습니다. 이제 메뉴에는 $split_at인수로 전달 된 정확한 숫자 가 기본적으로 5로 표시됩니다.
— gmazzap

10

CSS만으로도 이것을 가능하게하는 방법이 있습니다. 여기에는 몇 가지 한계가 있지만 여전히 흥미로운 접근법이라고 생각했습니다.

한계

  • 드롭 다운 너비를 하드 코딩해야합니다
  • 브라우저 지원. 기본적으로 CSS3 선택기 가 필요합니다 . 그러나 이것을 테스트하지는 않았지만 IE8의 모든 것이 작동해야합니다.
  • 이것은 더 많은 개념 증명입니다. 하위 항목이없는 경우에만 작동하는 것과 같은 몇 가지 단점이 있습니다.

접근하다

"Quantity Queries"를 실제로 사용하지는 않지만 창의적 사용법을 사용 :nth-child하고 CSS~ 의 최근 Quantity Queries를 읽었습니다. 솔루션으로 이끌었습니다.

접근 방식은 기본적으로 다음과 같습니다.

  1. 4 일 이후에 모든 항목 숨기기
  2. 를 ...사용하여 점 추가before의사 요소를 .
  3. 점 (또는 숨겨진 요소)을 가리키면 하위 메뉴라는 추가 항목이 표시됩니다.

기본 WordPress 메뉴 마크 업에 대한 CSS 코드는 다음과 같습니다. 인라인으로 댓글을 달았습니다.

/* 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;
}

나는 또한 그것을 보여주는 jsfiddle을 만들었습니다 : http://jsfiddle.net/jg6pLfd1/

이것이 어떻게 작동하는지 더 궁금한 점이 있으면 의견을 남겨주세요. 코드를 더 명확하게 설명 드리겠습니다.


접근 해 주셔서 감사합니다. CSS로 이미 생각했지만 PHP에서 직접 수행하는 것이 더 깨끗하다고 ​​생각합니다. 추가로이 솔루션은 5 번째 메뉴 포인트를 하위 메뉴에 넣습니다. 또한 5 개의 메뉴 항목 만 있으면 필요하지 않습니다.
— Snowball

5 + 항목에만 활성화하면 수정 될 수 있습니다. 어쨌든 나는 이것이 완벽하지 않으며 PHP 접근 방식이 더 깨끗하다는 것을 알고 있습니다. 그러나 나는 여전히 완전성을 위해 그것을 포함시키기에 충분히 흥미로웠다. 다른 옵션은 항상 좋습니다. :)
— kraftner

2
물론이야. Btw. 이것에 또 다른 하위 메뉴를 추가하면 역시 깨집니다
— Snowball

1
확실한. 이것은 지금까지 개념 증명에 더 가깝습니다. 경고를 추가했습니다.
— kraftner

8

wp_nav_menu_items필터 를 사용할 수 있습니다 . 메뉴 슬러그, 컨테이너 등과 같은 메뉴 속성을 보유하는 메뉴 출력 및 인수를 허용합니다.

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 <li></li> elements from menu output
    preg_match_all('/<li[^>]*>.*?<\/li>/iU', $items, $matches);

    // if menu has less the 5 items, just do nothing
    if (! isset($matches[0][5])) {
        return $items;
    }

    // add <ul> after 5th item (can be any number - can use e.g. site-wide variable)
    $matches[0][5] = '<li class="menu-item menu-item-type-custom">&hellip;<ul>'
          . $matches[0][5];

    // $matches contain multidimensional array
    // first (and only) item is found matches array
    return implode('', $matches[0]) . '</ul></li>';
}

1
몇 가지 사소한 문제를 편집했지만 모든 메뉴 항목에 하위 항목이없는 경우에만 작동합니다. 정규식은 계층 구조를 인식하지 않기 때문입니다. 그것을 테스트하십시오 : 처음 4 개의 메뉴 항목 중 하나에 하위 항목이 포함되어 있으면 메뉴가 완전히 파괴됩니다.
— gmazzap

1
사실입니다. 이 경우 DOMDocument사용할 수 있습니다. 그러나이 질문에는 하위 메뉴가 없으므로이 특정 경우에 대한 답변이 정확합니다. DOMDocument는 "범용"솔루션이지만 지금은 시간이 없습니다. 당신이 조사 할 수있다) LI 아이템을 루핑하는 것, 만약 UL 자식이 그것을 생략한다면, 그것은 해결책이지만 서면 버전이 필요하다 :)
— mjakic

1
(a) OP에 하위 메뉴가 있는지 알 수 없습니다. 마우스가 끝나면 하위 메뉴가 나타납니다. (b) 예, DOMDocument가 작동 할 수 있지만,이 경우 항목을 재귀 적으로 반복하여 inner 검사를 수행해야합니다 ul. WordPress는 이미 메뉴 워커에서 메뉴 항목을 반복합니다. 그것은 이미 느린 작동 그 자체 이며, 추가 및 추가 루프가 올바른 솔루션이 아니라고 생각합니다. 국가에서는 맞춤형 워커가 훨씬 깨끗하고 효율적인 솔루션입니다.
— gmazzap

고마워.하지만 @gmazzap은 사실이지만 다른 메뉴 포인트 (첫 번째 4 또는 다른 메뉴 포인트)에 다른 하위 메뉴가 포함될 가능성이 있습니다. 그래서이 영혼은 작동하지 않습니다.
— Snowball

메인 메뉴와 "숨겨진"메뉴 두 개를 배치 할 수도 있습니다. 세 개의 점 "..."이있는 스타일이 지정된 단추를 추가하고 클릭하거나 마우스를 올리면 두 번째 메뉴가 표시됩니다. 매우 쉬워야합니다.
— mjakic

5

작동하는 기능이 있지만 최상의 솔루션인지 확실하지 않습니다.

커스텀 워커를 사용했습니다.

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 ) : '';

    $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, $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 <ul>
     */
    global $menu_items;
    $menu_items = substr_count($output,'<li');
    if ($menu_items == 4) {
      $output .= '<li class="tooltip"><span>...</span><ul class="tooltip-menu">';
    }

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

    $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 .= '<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 );

  }
}

실제 메뉴를 표시하는 기능은 다음과 같습니다.

        <?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 </li> and </ul> if there are more then 4 items in the menu
        if ($menu_items > 4) {
            echo "</li></ul>";
        }
        ?>

전역 변수 $ menu_items를 선언하고 닫기 <li>및 <ul>-tags 를 표시하는 데 사용했습니다 . 커스텀 워커 내부에서도 그렇게 할 수는 있지만 어디에서 어떻게 찾을 수 없었습니다.

두 가지 문제 : 1. 메뉴에 5 개의 항목 만 있으면 마지막 항목은 물론 필요하지 않은 항목으로 감 쌉니다.

  1. 사용자가 theme_location에 메뉴를 실제로 할당 한 경우에만 작동하며 wp_nav_menu가 폴백 기능을 표시하면 워커가 실행되지 않습니다.

처음 4 개 항목 중 일부에 하위 메뉴가 있으면 어떻게됩니까? 팁 : 잘못된 장소에 substr_count($output,'<li')있을 것 == 4입니다.
— gmazzap
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.