研究 wp_nav_menu 如何解析自定义 Walker 类渲染层级结构

WordPress 导航菜单:自定义 Walker 类解析层级结构

大家好!今天我们来深入探讨 WordPress 的 wp_nav_menu 函数以及如何利用自定义 Walker 类来渲染层级结构的导航菜单。wp_nav_menu 是 WordPress 中用于显示导航菜单的核心函数,而 Walker 类则允许我们定制菜单项的 HTML 结构和层级显示方式。

wp_nav_menu 的基本原理

wp_nav_menu 函数的主要任务是从数据库中检索菜单项,并将这些菜单项传递给一个 Walker 类进行处理。 Walker 类负责遍历菜单项,并根据菜单项的属性(如标题、链接、父级 ID 等)生成 HTML 代码。

wp_nav_menu 函数接受一个参数数组,我们可以通过这个数组来配置菜单的各种属性,包括菜单 ID、容器元素、CSS 类、以及最重要的 Walker 类。

wp_nav_menu( array(
    'theme_location'  => 'primary', // 主题位置
    'menu'            => '', // 菜单 ID 或别名
    'container'       => 'div', // 容器元素
    'container_class' => 'menu-container', // 容器 CSS 类
    'container_id'    => '', // 容器 ID
    'menu_class'      => 'menu', // 菜单 CSS 类
    'menu_id'         => '', // 菜单 ID
    'echo'            => true, // 是否输出 HTML
    'fallback_cb'     => 'wp_page_menu', // 回调函数,当菜单不存在时使用
    'before'          => '', // 链接前的内容
    'after'           => '', // 链接后的内容
    'link_before'     => '', // 链接文字前的内容
    'link_after'      => '', // 链接文字后的内容
    'items_wrap'      => '<ul id="%1$s" class="%2$s">%3$s</ul>', // 菜单项的 HTML 结构
    'depth'           => 0, // 菜单深度
    'walker'          => new My_Custom_Walker(), // 自定义 Walker 类
) );

Walker 类的作用和结构

Walker 类是 WordPress 中用于遍历和处理树形结构的抽象类。它定义了一系列方法,这些方法在遍历树形结构时会被自动调用。对于 wp_nav_menu 来说,Walker 类负责遍历菜单项,并生成相应的 HTML 代码。

WordPress 提供了一个默认的 Walker 类 Walker_Nav_Menu,它用于生成标准的导航菜单。但是,我们可以通过创建自定义的 Walker 类来覆盖默认行为,从而实现更灵活的菜单渲染。

一个基本的自定义 Walker 类需要继承 Walker_Nav_Menu 类,并重写以下几个关键方法:

  • start_lvl( &$output, $depth = 0, $args = array() ):在子菜单开始时调用。
  • end_lvl( &$output, $depth = 0, $args = array() ):在子菜单结束时调用。
  • start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ):在每个菜单项开始时调用。
  • end_el( &$output, $item, $depth = 0, $args = array() ):在每个菜单项结束时调用。

这些方法接收一些参数,包括:

  • $output:用于累积 HTML 代码的字符串。
  • $item:当前菜单项的对象。
  • $depth:当前菜单项的深度。
  • $args:传递给 wp_nav_menu 函数的参数数组。
  • $id:当前菜单项的 ID。

创建自定义 Walker 类

下面是一个简单的自定义 Walker 类的示例,它会在每个菜单项的前后添加一些自定义的 HTML 代码。

class My_Custom_Walker extends Walker_Nav_Menu {

    function start_lvl( &$output, $depth = 0, $args = array() ) {
        $indent = str_repeat("t", $depth);
        $output .= "n$indent<ul class="sub-menu">n";
    }

    function end_lvl( &$output, $depth = 0, $args = array() ) {
        $indent = str_repeat("t", $depth);
        $output .= "$indent</ul>n";
    }

    function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
        $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 ) . '"' : '';

        $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 . '"';
            }
        }

        $title = apply_filters( 'the_title', $item->title, $item->ID );

        $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 );
    }

    function end_el( &$output, $item, $depth = 0, $args = array() ) {
        $output .= "</li>n";
    }
}

要使用这个自定义 Walker 类,我们需要将它传递给 wp_nav_menu 函数:

wp_nav_menu( array(
    'theme_location'  => 'primary',
    'walker'          => new My_Custom_Walker(),
) );

深入解析层级结构

Walker 类最重要的功能之一是处理菜单项的层级结构。$depth 参数在 start_lvl, end_lvl, start_el, end_el 方法中都可用,它表示当前菜单项的深度。我们可以根据 $depth 的值来调整 HTML 代码的生成,从而实现不同的层级显示效果。

例如,我们可以根据 $depth 的值来添加不同的 CSS 类:

class My_Custom_Walker extends Walker_Nav_Menu {
    function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
        $indent = ( $depth ) ? str_repeat( "t", $depth ) : '';

        $classes = empty( $item->classes ) ? array() : (array) $item->classes;
        $classes[] = 'menu-item-' . $item->ID;
        $classes[] = 'menu-depth-' . $depth; // 添加深度相关的 CSS 类

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

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

        // ... 剩下的代码
    }
}

这样,每个菜单项都会有一个 menu-depth-X 的 CSS 类,其中 X 是菜单项的深度。我们可以使用 CSS 来根据深度来设置不同的样式。

高级技巧:使用 Walker::has_children()

Walker 类提供了一个 has_children() 方法,可以用来判断一个菜单项是否有子菜单。这个方法在 Walker_Nav_Menu 类中没有直接使用,但我们可以在自定义 Walker 类中使用它来添加额外的 HTML 代码或 CSS 类。

class My_Custom_Walker extends Walker_Nav_Menu {
    function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
        $indent = ( $depth ) ? str_repeat( "t", $depth ) : '';

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

        if ( $args->walker->has_children( $item->ID, $args->menu->term_id ) ) {
            $classes[] = 'menu-item-has-children'; // 添加有子菜单的 CSS 类
        }

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

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

        // ... 剩下的代码
    }
}

在这个例子中,我们使用 has_children() 方法来判断当前菜单项是否有子菜单,如果有,就添加一个 menu-item-has-children 的 CSS 类。

实际案例:面包屑导航

Walker 类不仅可以用于渲染导航菜单,还可以用于生成其他类型的树形结构,例如面包屑导航。

class Breadcrumb_Walker extends Walker {
    var $tree_type = array( 'post_type', 'taxonomy', 'custom' );
    var $db_fields = array( 'parent' => 'menu_item_parent', 'id' => 'db_id' );

    function start_lvl( &$output, $depth = 0, $args = array() ) {
        $indent = str_repeat("t", $depth);
        $output .= "n$indent<ul class="breadcrumb-list">n";
    }

    function end_lvl( &$output, $depth = 0, $args = array() ) {
        $indent = str_repeat("t", $depth);
        $output .= "$indent</ul>n";
    }

    function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
        $output .= '<li><a href="' . esc_url( $item->url ) . '">' . esc_html( $item->title ) . '</a></li>';
    }

    function end_el( &$output, $item, $depth = 0, $args = array() ) {
        // Nothing to do.
    }
}

// 使用示例
$menu_items = wp_get_nav_menu_items( 'primary' ); // 获取菜单项
$breadcrumb_walker = new Breadcrumb_Walker();
$breadcrumb = '<ul class="breadcrumb">';
$breadcrumb .= $breadcrumb_walker->walk( $menu_items, 0 ); // 调用 walk 方法
$breadcrumb .= '</ul>';

echo $breadcrumb;

这个例子展示了如何使用 Walker 类来生成面包屑导航。需要注意的是,我们直接使用了 Walker 基类,而不是 Walker_Nav_Menu 类,因为面包屑导航不需要处理菜单项的 CSS 类和 ID。

总结和进阶

自定义 Walker 类是 WordPress 中一个非常强大的工具,可以让我们灵活地控制导航菜单的渲染。通过重写 start_lvl, end_lvl, start_el, end_el 方法,我们可以根据菜单项的属性和深度来生成不同的 HTML 代码。Walker::has_children() 方法可以用来判断菜单项是否有子菜单,从而添加额外的 HTML 代码或 CSS 类。

希望今天的讲座能够帮助大家更好地理解 wp_nav_menu 函数和自定义 Walker 类。 熟练运用自定义 Walker 类将使你能更精细地控制 WordPress 网站的导航菜单,并实现更复杂的页面结构和交互效果。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注