在 WordPress 自定义 walker 中向 Bootstrap 导航添加附加按钮
Add additional button to Bootstrap navigation in WordPress custom walker
我正在使用以下 WordPress 自定义导航 walker 代码生成 bootstrap 导航:
<?php
class NavWalker extends Walker_Nav_Menu {
private $cpt; // Boolean, is current post a custom post type
private $archive; // Stores the archive page for current URL
public function __construct() {
add_filter('nav_menu_css_class', array($this, 'cssClasses'), 10, 2);
add_filter('nav_menu_link_attributes', array($this, 'filter_menu_link_atts'), 10, 3);
add_filter('walker_nav_menu_start_el', array($this, 'filter_menu_item_output'), 10, 4);
add_filter('nav_menu_item_id', '__return_null');
$cpt = get_post_type();
$this->cpt = in_array($cpt, get_post_types(array('_builtin' => false)));
$this->archive = get_post_type_archive_link($cpt);
}
/**
* @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.
*/
// @codingStandardsIgnoreStart
public function start_lvl(&$output, $depth = 0, $args = array()) {
parent::start_lvl($output, $depth, $args);
$pos = strrpos($output, '">', -1);
$output = substr_replace($output, ' dropdown-menu depth'.$depth.'" role="menu">', $pos);
}
// @codingStandardsIgnoreEnd
/**
* Traverse elements to create list from elements.
*
* Display one element if the element doesn't have any children otherwise,
* display the element and its children. Will only traverse up to the max
* depth and no ignore elements under that depth.
*
* This method shouldn't be called directly, use the walk() method instead.
*
* @see Walker::start_el()
* @since 2.5.0
*
* @param object $element Data object
* @param array $children_elements List of elements to continue traversing.
* @param int $max_depth Max depth to traverse.
* @param int $depth Depth of current element.
* @param array $args
* @param string $output Passed by reference. Used to append additional content.
* @return null Null on failure with no changes to parameters.
*/
// @codingStandardsIgnoreStart
public function display_element($element, &$children_elements, $max_depth, $depth, $args, &$output) {
if (!$element) {
return;
}
$id_field = $this->db_fields['id'];
$this->has_children = !empty($children_elements[$element->$id_field]);
parent::display_element($element, $children_elements, $max_depth, $depth, $args, $output);
}
// @codingStandardsIgnoreEnd
public function cssClasses($classes, $item) {
if ($this->has_children) {
$classes[] = 'dropdown';
}
if ($item->menu_item_parent != 0) {
$classes[] = 'dropdown-item';
}
if ($item->menu_item_parent == 0) {
$classes[] = 'nav-item';
}
if (strcasecmp($item->attr_title, 'header') == 0) {
$classes[] = 'dropdown-header';
}
$slug = sanitize_title($item->title);
if ($this->cpt) {
$classes = str_replace('current_page_parent', '', $classes);
if (strcasecmp($this->archive, $item->url) === 0) {
$classes[] = 'active';
}
}
//$classes = preg_replace('/(current(-menu-|[-_]page[-_])(item|parent|ancestor))/', '', $classes);
$classes = preg_replace('/^((menu|page)[-_\w+]+)+/', '', $classes);
$classes[] = 'menu-' . $slug;
$classes = array_unique($classes);
return array_filter($classes, function ($element) {
$element = trim($element);
return !empty($element);
});
}
/*
* This filter is used to customize the <li> attributes.
*/
// @codingStandardsIgnoreStart
public function filter_menu_link_atts($atts, $item, $args) {
if (is_object($args->walker)) { // Filter if custom walker
$classes = array();
$classes[] = 'nav-link';
/*
if ($args->walker->has_children) {
$atts['data-toggle'] = 'dropdown';
$classes[] = 'dropdown-toggle';
$atts['aria-haspopup'] = 'true';
}
*/
if (strcasecmp($item->attr_title, 'disabled') == 0) {
$classes[] = 'disabled';
}
if ($item->current == 1) {
$classes[] = 'active';
}
if ($item->parent == 1) {
$classes[] = 'active';
}
$atts['class'] = implode(' ', $classes);
}
return $atts;
}
// @codingStandardsIgnoreEnd
/*
* This filter is used to customize the <li> final output.
*/
// @codingStandardsIgnoreStart
public function filter_menu_item_output($item_output, $item, $depth, $args) {
$indent = str_repeat("\t", $depth);
if (strcasecmp($item->attr_title, 'divider') == 0 && $depth === 1) {
$item_output .= $indent . '<li role="presentation" class="dropdown-divider">';
} else if ((strcasecmp($item->attr_title, 'header') == 0 && $depth === 1) && $depth === 1) {
$item_output = $indent . '<h6>' . esc_attr($item->title) . '</h6>';
}
$args->link_before = '';
$args->link_after = '';
return $indent . $item_output;
}
// @codingStandardsIgnoreEnd
}
我还使用以下 JS 代码在鼠标悬停时打开导航中的下拉菜单:
$('ul.nav li.dropdown.nav-item').hover(function() {
$(this).find('.sub-menu.dropdown-menu.depth0').stop(true, true).delay(200).fadeIn(500);
}, function() {
$(this).find('.sub-menu.dropdown-menu.depth0').stop(true, true).delay(200).fadeOut(500);
});
$('li.dropdown.dropdown-item').hover(function() {
$(this).find('.sub-menu.dropdown-menu.depth1').stop(true, true).delay(200).fadeIn(500);
}, function() {
$(this).find('.sub-menu.dropdown-menu.depth1').stop(true, true).delay(200).fadeOut(500);
});
在这种情况下,移动用户没有机会打开下拉菜单。他总是会点击link。这对我来说绝对没问题。我只需要一个 link 供移动用户使用(右侧的箭头图标)让他们打开下拉菜单。
我只是不明白我必须在自定义助行器中的什么地方添加这个 link。
我找到了解决办法。更改此代码:
public function start_lvl(&$output, $depth = 0, $args = array()) {
parent::start_lvl($output, $depth, $args);
$pos = strrpos($output, '">', -1);
$output = substr_replace($output, ' dropdown-menu depth'.$depth.'" role="menu">', $pos);
}
对此:
public function start_lvl(&$output, $depth = 0, $args = array()) {
$indent = str_repeat("\t", $depth);
$output .= "\n$indent<button class=\"btn btn-link btn-menu pull-xs-right hidden-lg-up\" data-toggle=\"dropdown\"><i class=\"icon-chevron-down\"></i></button>\n";
parent::start_lvl($output, $depth, $args);
$pos = strrpos($output, '">', -1);
$output = substr_replace($output, ' dropdown-menu depth'.$depth.'" role="menu">', $pos);
}
我正在使用以下 WordPress 自定义导航 walker 代码生成 bootstrap 导航:
<?php
class NavWalker extends Walker_Nav_Menu {
private $cpt; // Boolean, is current post a custom post type
private $archive; // Stores the archive page for current URL
public function __construct() {
add_filter('nav_menu_css_class', array($this, 'cssClasses'), 10, 2);
add_filter('nav_menu_link_attributes', array($this, 'filter_menu_link_atts'), 10, 3);
add_filter('walker_nav_menu_start_el', array($this, 'filter_menu_item_output'), 10, 4);
add_filter('nav_menu_item_id', '__return_null');
$cpt = get_post_type();
$this->cpt = in_array($cpt, get_post_types(array('_builtin' => false)));
$this->archive = get_post_type_archive_link($cpt);
}
/**
* @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.
*/
// @codingStandardsIgnoreStart
public function start_lvl(&$output, $depth = 0, $args = array()) {
parent::start_lvl($output, $depth, $args);
$pos = strrpos($output, '">', -1);
$output = substr_replace($output, ' dropdown-menu depth'.$depth.'" role="menu">', $pos);
}
// @codingStandardsIgnoreEnd
/**
* Traverse elements to create list from elements.
*
* Display one element if the element doesn't have any children otherwise,
* display the element and its children. Will only traverse up to the max
* depth and no ignore elements under that depth.
*
* This method shouldn't be called directly, use the walk() method instead.
*
* @see Walker::start_el()
* @since 2.5.0
*
* @param object $element Data object
* @param array $children_elements List of elements to continue traversing.
* @param int $max_depth Max depth to traverse.
* @param int $depth Depth of current element.
* @param array $args
* @param string $output Passed by reference. Used to append additional content.
* @return null Null on failure with no changes to parameters.
*/
// @codingStandardsIgnoreStart
public function display_element($element, &$children_elements, $max_depth, $depth, $args, &$output) {
if (!$element) {
return;
}
$id_field = $this->db_fields['id'];
$this->has_children = !empty($children_elements[$element->$id_field]);
parent::display_element($element, $children_elements, $max_depth, $depth, $args, $output);
}
// @codingStandardsIgnoreEnd
public function cssClasses($classes, $item) {
if ($this->has_children) {
$classes[] = 'dropdown';
}
if ($item->menu_item_parent != 0) {
$classes[] = 'dropdown-item';
}
if ($item->menu_item_parent == 0) {
$classes[] = 'nav-item';
}
if (strcasecmp($item->attr_title, 'header') == 0) {
$classes[] = 'dropdown-header';
}
$slug = sanitize_title($item->title);
if ($this->cpt) {
$classes = str_replace('current_page_parent', '', $classes);
if (strcasecmp($this->archive, $item->url) === 0) {
$classes[] = 'active';
}
}
//$classes = preg_replace('/(current(-menu-|[-_]page[-_])(item|parent|ancestor))/', '', $classes);
$classes = preg_replace('/^((menu|page)[-_\w+]+)+/', '', $classes);
$classes[] = 'menu-' . $slug;
$classes = array_unique($classes);
return array_filter($classes, function ($element) {
$element = trim($element);
return !empty($element);
});
}
/*
* This filter is used to customize the <li> attributes.
*/
// @codingStandardsIgnoreStart
public function filter_menu_link_atts($atts, $item, $args) {
if (is_object($args->walker)) { // Filter if custom walker
$classes = array();
$classes[] = 'nav-link';
/*
if ($args->walker->has_children) {
$atts['data-toggle'] = 'dropdown';
$classes[] = 'dropdown-toggle';
$atts['aria-haspopup'] = 'true';
}
*/
if (strcasecmp($item->attr_title, 'disabled') == 0) {
$classes[] = 'disabled';
}
if ($item->current == 1) {
$classes[] = 'active';
}
if ($item->parent == 1) {
$classes[] = 'active';
}
$atts['class'] = implode(' ', $classes);
}
return $atts;
}
// @codingStandardsIgnoreEnd
/*
* This filter is used to customize the <li> final output.
*/
// @codingStandardsIgnoreStart
public function filter_menu_item_output($item_output, $item, $depth, $args) {
$indent = str_repeat("\t", $depth);
if (strcasecmp($item->attr_title, 'divider') == 0 && $depth === 1) {
$item_output .= $indent . '<li role="presentation" class="dropdown-divider">';
} else if ((strcasecmp($item->attr_title, 'header') == 0 && $depth === 1) && $depth === 1) {
$item_output = $indent . '<h6>' . esc_attr($item->title) . '</h6>';
}
$args->link_before = '';
$args->link_after = '';
return $indent . $item_output;
}
// @codingStandardsIgnoreEnd
}
我还使用以下 JS 代码在鼠标悬停时打开导航中的下拉菜单:
$('ul.nav li.dropdown.nav-item').hover(function() {
$(this).find('.sub-menu.dropdown-menu.depth0').stop(true, true).delay(200).fadeIn(500);
}, function() {
$(this).find('.sub-menu.dropdown-menu.depth0').stop(true, true).delay(200).fadeOut(500);
});
$('li.dropdown.dropdown-item').hover(function() {
$(this).find('.sub-menu.dropdown-menu.depth1').stop(true, true).delay(200).fadeIn(500);
}, function() {
$(this).find('.sub-menu.dropdown-menu.depth1').stop(true, true).delay(200).fadeOut(500);
});
在这种情况下,移动用户没有机会打开下拉菜单。他总是会点击link。这对我来说绝对没问题。我只需要一个 link 供移动用户使用(右侧的箭头图标)让他们打开下拉菜单。
我只是不明白我必须在自定义助行器中的什么地方添加这个 link。
我找到了解决办法。更改此代码:
public function start_lvl(&$output, $depth = 0, $args = array()) {
parent::start_lvl($output, $depth, $args);
$pos = strrpos($output, '">', -1);
$output = substr_replace($output, ' dropdown-menu depth'.$depth.'" role="menu">', $pos);
}
对此:
public function start_lvl(&$output, $depth = 0, $args = array()) {
$indent = str_repeat("\t", $depth);
$output .= "\n$indent<button class=\"btn btn-link btn-menu pull-xs-right hidden-lg-up\" data-toggle=\"dropdown\"><i class=\"icon-chevron-down\"></i></button>\n";
parent::start_lvl($output, $depth, $args);
$pos = strrpos($output, '">', -1);
$output = substr_replace($output, ' dropdown-menu depth'.$depth.'" role="menu">', $pos);
}