Home WordpressWordPress cơ bản Tùy biến hiển thị menus trong wordpress – Nav Menu Walker

Tùy biến hiển thị menus trong wordpress – Nav Menu Walker

by admincp

Nếu website của bạn có một hệ thống menu lớn, bạn muốn hiển thị một phần dữ liệu menu ở một khu vực nào đó trên trang/ một trang khác. Bài viết này sẽ giải thích chi tiết cách làm này. Ý tưởng hiện ra trong đầu bạn là tạo từng menu cho mỗi trang, nhưng tại sao lại làm cách này chia ra làm nhiều menus sẽ khó quản lý trong khi wordpress có API cho phép thiết lập trạng thái cho từng menu item.

Xóa ul wrap
Xóa ul bao menu chứa thẻ li, bạn điều chỉnh tham số ‘items_wrap’

<?php wp_nav_menu( array( 'items_wrap' => '%3$s' ) ); ?>

Hoặc
Thêm ký tự trước menu

<?php wp_nav_menu( array( 'theme_location' => 'primary', 'items_wrap' => '<ul><li id="item-id">Menu: </li>%3$s</ul>' ) ); ?>

Tạo một class mới trong /functions.php , tên lớp nên có tiền tố để tránh trùng với class khác.

class JC_Walker_Nav_Menu extends Walker_Nav_Menu {
}

Trong Class Walker_Nav_Menu có các hàm bạn có thể kế thừa và override, lớp được viết ở /wp-admin/includes/nav-menu.php
Ví dụ sau đây mình sẽ lấy tất cả menu items là con của 1 menu chỉ định, để làm điều này cần kế thừa 2 hàm của lớp là: start_llvlend_lvl.

class JC_Walker_Nav_Menu extends Walker_Nav_Menu {

	function start_lvl( &$output, $depth = 0, $args = array() ) {}

	function end_lvl( &$output, $depth = 0, $args = array() ) {}
}

Truyền parent menu id vào construct để lấy xác định các menu item con, tiếp tục lấy các menu items ids con làm parent id để xác định các menu item con của nó, cứ thế cho đến hết, dùng mảng lưu tất cả các menu items cho việc quản lý.

class JC_Walker_Nav_Menu extends Walker_Nav_Menu {

	var $menu_id = 0;
	var $menu_items = array();

	function __construct($id = 0){
		$this->menu_id = $id;
		$this->menu_items[] = $id;
	}

	function start_el( &$output, $item, $depth, $args ) {
		if( !empty($this->menu_items) && !in_array($item->ID, $this->menu_items) && !in_array($item->menu_item_parent, $this->menu_items)){
			return false;
		}

		if(!in_array($item->ID, $this->menu_items)){
			$this->menu_items[] = $item->ID;
		}

		parent::start_el($output, $item, $depth, $args);
	}

	function end_el( &$output, $item, $depth = 0, $args = array() ) {
		if( !empty($this->menu_items) && !in_array($item->ID, $this->menu_items) && !in_array($item->menu_item_parent, $this->menu_items)){
    			return false;
    		}
    		parent::end_el($output, $item, $depth, $args);
	}

	function start_lvl( &$output, $depth = 0, $args = array() ) {}

	function end_lvl( &$output, $depth = 0, $args = array() ) {}
}

Hiển thị nav menu với wp_nav_menu, khởi tạo class JC_Walker_Nav_Menu và truyền parent menu id=8 để chỉ hiển thị toàn bộ các menu dưới menu có id=8.

<?php  wp_nav_menu( array('menu' => 'header-menu', 'walker' => new JC_Walker_Nav_Menu(8)) ); ?>

Bạn có thể extends walker class để thay đổi nội dung hiển thị HTML cho item và thậm trí sub items tạo bởi các hàm như: wp_nav_menu, wp_list_pages, wp_list_categories.

Bằng cách sửa đổi nội dung của các hàm thiết kế của lớp kế thừa, chúng ta sẽ làm thay đổi cách hiển thị cho các dữ liệu Menu, Category trong wordpress.
Phương thức start_el mở thẻ li bắt đầu cho một item và tương ứng đóng item với phương thức end_el. Sử dụng tham số xác định thông tin và dữ liệu của item bạn có thể loại bỏ nó trong kết quả hiển thị. Ví dụ sau có loại bỏ top-level elements.

// Don't print top-level elements
function start_el(&$output, $item, $depth=0, $args=array()) {
    if( 0 == $depth )
        return;
    parent::start_el(&$output, $item, $depth, $args);
}

function end_el(&$output, $item, $depth=0, $args=array()) {
    if( 0 == $depth )
        return;
    parent::end_el(&$output, $item, $depth, $args);
}

Walker class còn có phương thức display_element, phương thức này giúp sử lý những item có chứa sub items bên dưới. Hàm display_element kế thừa trong class chính được chạy trong hàm start_el nếu có kế thừa tính năng của hàm mẹ start_el. Trong cách sử dụng php class, chúng ta gọi hàm mẹ của phương thức kế thừa với từ khóa parent, giống như thế này:

// Don't print top-level elements
function start_el(&$output, $item, $depth=0, $args=array()) {
    if( 0 == $depth )
        return;
    parent::start_el(&$output, $item, $depth, $args);
}

Tham khảo mẫu nội dung đầy đủ của hàm start_el và end_el.

var $number = 1;

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

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

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

        // add span with number here
        if ( $depth == 0 ) { // remove if statement if depth check is not required
            $output .= sprintf( '<span>%02s.</span>', $this->number++ );
        }

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

        $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 );
    }
function end_el(&$output, $item, $depth=0, $args=array()) {
		$indent = ( $depth ) ? str_repeat( "t", $depth ) : '';
        $output .= $indent."</li>n";
    }

Mọi item đều sử lý qua hàm display_element này. Ví dụ, chúng ta không cho hiển thị các menu ở top-level và URL hiện tại không thuộc về item đó. Để làm điều này, chúng ta dựa vào thuộc tính class : current-menu-item, current-menu-parent, current-menu-ancestor

// Only follow down one branch
function display_element( $element, &$children_elements, $max_depth, $depth=0, $args, &$output ) {

    // Check if element as a 'current element' class
    $current_element_markers = array( 'current-menu-item', 'current-menu-parent', 'current-menu-ancestor' );
    $current_class = array_intersect( $current_element_markers, $element->classes );

    // If element has a 'current' class, it is an ancestor of the current element
    $ancestor_of_current = !empty($current_class);

    // If this is a top-level link and not the current, or ancestor of the current menu item - stop here.
    if ( 0 == $depth && !$ancestor_of_current)
        return;

    parent::display_element( $element, &$children_elements, $max_depth, $depth, $args, &$output );
}

Hai phương thức cuối cùng nhóm các sub items ở mọi levels, là start_lvl & end_lvl. Thường sử dụng thẻ ul. Nếu bạn cũng muốn loại bỏ cho nhóm items ở top-level. Lưu ý: toàn bộ những items trong level đó sẽ bỏ qua, không được hiển thị.

// Don't wrap the top level
function start_lvl(&$output, $depth=0, $args=array()) {
    if( 0 == $depth )
        return;
    parent::start_lvl(&$output, $depth, $args);
}

function end_lvl(&$output, $depth=0, $args=array()) {
    if( 0 == $depth )
        return;
    parent::end_lvl(&$output, $depth, $args);
}

Kế thừa nội dung của phương thức tạo thẻ bao items ở trên, đầy đủ giống như sau.

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

Bạn để ý ngoài cách sử dụng class walker vào menu, bạn có thể chia nhỏ can thiệp vào từng dữ liệu có trong walker mặc định của menu với các hooks filter.

walker_nav_menu_start_el

Chúng ta để ý có filter ‘walker_nav_menu_start_el‘ cho phép bạn tùy biến hiển thị menu bằng cách sửa hook này. Bạn có thể thêm vào functions.php như sau, ví dụ:

if (function_exists('qtrans_convertURL')) {

function qtrans_in_nav_el($output, $item, $depth, $args) {

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

	// Integration with qTranslate Plugin

	$attributes .=!empty($item->url) ? ' href="' . esc_attr( qtrans_convertURL($item->url) ) . '"' : '';

	$output = $args->before;

	$output .= '<a' . $attributes . '>';

	$output .= $args->link_before . apply_filters('the_title', $item->title, $item->ID) . $args->link_after;

	$output .= '</a>';

	$output .= $args->after;

	return $output;

}

add_filter('walker_nav_menu_start_el', 'qtrans_in_nav_el', 10, 4);

}

nav_menu_link_attributes

Bạn có thể sửa mảng $atts là kết quả của filter nav_menu_link_attributes. Ví dụ sau mình fix lỗi URL trang chủ khi chuyển ngôn ngữ trong wordpress cho plugin qtranslate, bạn thêm đoạn code sau vào functions.php

function qtrans_convertHomeURL($url, $what) {
    if($what=='/') return qtrans_convertURL($url);
    return $url;
}

add_filter('home_url', 'qtrans_convertHomeURL', 10, 2);
add_filter('nav_menu_link_attributes','custom_menu',10,3);
function custom_menu($atts, $item, $args){
	if($item->url=='/'){
		$atts['href']=qtrans_convertURL($item->url);
	}
	return $atts;
}

Bạn có thể sửa lại tham số cài đặt cho các menu tạo bởi wp_nav_menu, tùy chỉnh cấu hình của mọi menu sử dụng filter wp_nav_menu_args.

function modify_nav_menu_args( $args )
{
	if( 'primary' == $args['theme_location'] )
	{
		$args['depth'] = -1;
		$args['container_id'] = 'my_primary_menu';
	}

	return $args;
}

add_filter( 'wp_nav_menu_args', 'modify_nav_menu_args' );

Khi wp_nav_menu lấy giá trị mảng truyền vào, nó sẽ sử lý qua filter wp_nav_menu_args, do đó chúng ta có thể sửa lại mọi menu trước khi nó được hiển thị như đúng thiết lập tại vị trí gọi hàm hiển thị menu wp_nav_menu.

Tạo custom menu widget, cho lựa chọn hiển thị một phần của menu. Xem code widget sau đây:

<?php
class JC_Adv_Menu_Widget extends WP_Widget {

	public function __construct() {
		parent::__construct(
	 		'jc_menu_widget', // Base ID
			'Advanced Menu Widget', // Name
			array( 'description' => __( 'Custom options to output menus in your theme')) // Args
		);
	}

	public function widget( $args, $instance ) {
		extract( $args );

		$title = apply_filters( 'widget_title', $instance['title'] );

		echo $before_widget;

		if ( ! empty( $title ) )
			echo $before_title . $title . $after_title;

		wp_nav_menu( array('menu' => $instance['menu'], 'walker' => new JC_Walker_Nav_Menu($instance['menu_item'])) );

		echo $after_widget;
	}

 	public function form( $instance ) {

		$title = isset($instance['title']) ? $instance['title'] : '';
		$menu = isset($instance['menu']) ? $instance['menu'] : '';
		$menu_item = isset($instance['menu_item']) ? $instance['menu_item'] : '';
		$menus = get_terms('nav_menu');

		?>
		<p>
		<label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php _e( 'Title:' ); ?></label>
		<input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" type="text" value="<?php echo esc_attr( $title ); ?>" />
		</p>

		<p>
		<input class="widefat" id="<?php echo $this->get_field_id( 'menu' ); ?>" name="<?php echo $this->get_field_name( 'menu' ); ?>" type="hidden" value="<?php echo $menu; ?>" />
		</p>

		<p>
			<label for="<?php echo $this->get_field_id( 'menu_item' ); ?>"><?php _e( 'Select Menu Part:' ); ?></label>
			<select class="widefat" id="<?php echo $this->get_field_id( 'menu_item' ); ?>" name="<?php echo $this->get_field_name( 'menu_item' ); ?>" >
				<?php foreach($menus as $m){
					wp_nav_menu(array(
					  'menu' => $m->slug, // your theme location here
					  'container' => false,
					  'walker'         => new Walker_Nav_Menu_Dropdown($menu_item),
					  'items_wrap'     => '<optgroup id="'.$m->slug.'" label="'.$m->name.'">%3$s</optgroup>',
					));
				} ?>
			</select>
		</p>

		<script type="text/javascript">
		jQuery(document).ready(function($) {
			$('select#<?php echo $this->get_field_id( 'menu_item' ); ?>').change(function(){
				var label=$('select#<?php echo $this->get_field_id( 'menu_item' ); ?> :selected').parent().attr('id');
			    $('#<?php echo $this->get_field_id( 'menu' ); ?>').val(label);
			});
		});
		</script>

		<?php
	}

	public function update( $new_instance, $old_instance ) {
		$instance = array();

		$instance['title'] = strip_tags( $new_instance['title'] );
		$instance['menu'] = strip_tags( $new_instance['menu'] );
		$instance['menu_item'] = strip_tags( $new_instance['menu_item'] );

		return $instance;
	}

}

add_action( 'widgets_init', create_function( '', 'register_widget( "JC_Adv_Menu_Widget" );' ) );

class Walker_Nav_Menu_Dropdown extends Walker_Nav_Menu{

	var $item_id = 0;

	function __construct($id = 0){
		$this->item_id = $id;
	}

	public function start_lvl(&$output, $depth){}

	public function end_lvl(&$output, $depth){}

	public function start_el(&$output, $item, $depth, $args){

		$item->title = str_repeat("&nbsp;", $depth * 4) . $item->title;

		parent::start_el($output, $item, $depth, $args);
		if($item->ID == $this->item_id)
			$output = str_replace('<li', '<option value="'.$item->ID.'" selected="selected"', $output);
		else
			$output = str_replace('<li', '<option value="'.$item->ID.'"', $output);
	}

	public function end_el(&$output, $item, $depth){
		$output .= "</option>n";
	}
}
?>

Ngoài tính năng walker mà menu cung cấp, có thể sử dụng các hàm menu : wp_get_nav_menu_items, wp_get_nav_menu_object, get_nav_menu_locations..
để lấy dữ liệu menu.
Tham khảo:

function vcn_menus_items($id){
    static $data_menu=array();
    $locations = get_nav_menu_locations();
    $menu=wp_get_nav_menu_object($locations['menu1']);
    $args=array(
        "post_parent"=>$id
        //'post_type'=>'nav_menu_item',
        //'post_parent'=>0
    );
    $menu_items = wp_get_nav_menu_items($menu->term_id,$args);

    if(count($data_menu)==0)
    foreach( (array)$menu_items as $key=>$menu_item){print_r($menu_item);
   $t = wp_get_nav_menu_items($menu_item->object_id,array('post_parent'=>$menu_item->post_parent));print_r($t);
    echo '<hr/>';continue;
        echo $menu_item->post_type.'<br/>';
        echo $menu_item->object.'<br/>';
        echo $menu_item->url.'<br/>';
        echo $menu_item->menu_item_parent .'<br/>';
        echo $menu_item->title.'<hr/>';

		//các item theo thứ tự or trong cùng 1 menu item gốc:ie:
		/*
			-item 1	//theo thứ tự: ie (1)
				-item-1-0	(2)
				-item-1-1	(3)
				-item-1-2	(4)
				...
			-item 2	//khác menu_order	ie:(6)
		*/
		$element->menu_order

        //save
        if(!$menu_item->menu_item_parent) $data_menu[]=array('main'=>$menu_item,'sub'=>array());
        else $data_menu[count($data_menu)-1]['sub'][]=$menu_item;

        //echo '<li><a href="' . $url . '">' . $title . '</a></li>';
    }
    return $data_menu;
}

Hết !

Để nhận được bài viết mới vui lòng đăng ký kênh kiến thức WordPress từ A-Z ở Form bên dưới. Bạn cũng có thể nhận được sự trợ giúp trên TwitterFacebook

Liên hệ

Công ty chuyên Thiết kế website uy tín nhất Miền Bắc: http://vinastar.net

Hotline tư vấn: 0989 48 3456

Nguồn: Sưu tầm trên internet

You may also like

Leave a Comment