Header Ads Widget

Sử dụng Walker trong Menu

Sử dụng Walker trong Menu

Walker là một lớp trong WordPress để định hình cấu trúc của vài chức năng như cấu trúc menu, cấu trúc widget Category, cấu trúc widget Pages.


Để có thể chỉ định một menu nào đó hiển thị ra bên ngoài theme với cấu trúc theo cách cấu hình Walker_Nav_Menu của mình thì bạn phải kích hoạt nó bằng cách thêm tham số walker vào hàm wp_nav_menu.
$chiaseaz_walker = new Chiaseaz_Nav_Walker;
wp_nav_menu( array(
'theme_location' => 'primary',
'menu_class' => 'nav-menu',
'menu_id' => 'primary-menu',
'walker' => $chiaseaz_walker
) );
Tạo và kế thừa lại lớp Walker_Nav_Menu, và trong đó có một số phương thức trừu tượng như sau:
class Chiaseaz_Nav_Walker extends Walker_Nav_Menu {
/** * @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. */ public function start_lvl( &$output, $depth = 0, $args = array() ) { $indent = str_repeat( "\t", $depth ); $output .= "\n$indent<ul role=\"menu\" class=\" dropdown-menu\">\n"; }
/** * @see Walker::start_el() * @since 3.0.0 * * @param string $output Passed by reference. Used to append additional content. * @param object $item Menu item data object. * @param int $depth Depth of menu item. Used for padding. * @param int $current_page Menu item ID. * @param object $args */ public function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) { $indent = ( $depth ) ? str_repeat( "\t", $depth ) : '';
/** * Dividers, Headers or Disabled * ============================= * Determine whether the item is a Divider, Header, Disabled or regular * menu item. To prevent errors we use the strcasecmp() function to so a * comparison that is not case sensitive. The strcasecmp() function returns * a 0 if the strings are equal. */ if ( strcasecmp( $item->attr_title, 'divider' ) == 0 && $depth === 1 ) { $output .= $indent . '<li role="presentation" class="divider">'; } else if ( strcasecmp( $item->title, 'divider') == 0 && $depth === 1 ) { $output .= $indent . '<li role="presentation" class="divider">'; } else if ( strcasecmp( $item->attr_title, 'dropdown-header') == 0 && $depth === 1 ) { $output .= $indent . '<li role="presentation" class="dropdown-header">' . esc_attr( $item->title ); } else if ( strcasecmp($item->attr_title, 'disabled' ) == 0 ) { $output .= $indent . '<li role="presentation" class="disabled"><a href="#">' . esc_attr( $item->title ) . '</a>'; } else {
$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 ) );
if ( $args->has_children ) $class_names .= ' dropdown';
if ( in_array( 'current-menu-item', $classes ) ) $class_names .= ' active';
$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 .'>';
$atts = array(); $atts['title']  = ! empty( $item->title ) ? $item->title : ''; $atts['target'] = ! empty( $item->target ) ? $item->target : ''; $atts['rel']    = ! empty( $item->xfn ) ? $item->xfn : '';
// If item has_children add atts to a. if ( $args->has_children && $depth === 0 ) { $atts['href']   = '#'; $atts['data-toggle'] = 'dropdown'; $atts['class'] = 'dropdown-toggle'; $atts['aria-haspopup'] = 'true'; } else { $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;
/* * Glyphicons * =========== * Since the the menu item is NOT a Divider or Header we check the see * if there is a value in the attr_title property. If the attr_title * property is NOT null we apply it as the class name for the glyphicon. */ if ( ! empty( $item->attr_title ) ) $item_output .= '<a'. $attributes .'><span class="glyphicon ' . esc_attr( $item->attr_title ) . '"></span>&nbsp;'; else $item_output .= '<a'. $attributes .'>';
$item_output .= $args->link_before . apply_filters( 'the_title', $item->title, $item->ID ) . $args->link_after; $item_output .= ( $args->has_children && 0 === $depth ) ? ' <span class="caret"></span></a>' : '</a>'; $item_output .= $args->after;
$output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args ); } }
/** * 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. */ public function display_element( $element, &$children_elements, $max_depth, $depth, $args, &$output ) {        if ( ! $element )            return;
        $id_field = $this->db_fields['id'];
        // Display this element.        if ( is_object( $args[0] ) )           $args[0]->has_children = ! empty( $children_elements[ $element->$id_field ] );
        parent::display_element( $element, $children_elements, $max_depth, $depth, $args, $output );    }
/** * Menu Fallback * ============= * If this function is assigned to the wp_nav_menu's fallback_cb variable * and a manu has not been assigned to the theme location in the WordPress * menu manager the function with display nothing to a non-logged in user, * and will add a link to the WordPress menu manager if logged in as an admin. * * @param array $args passed from the wp_nav_menu function. * */ public static function fallback( $args ) { if ( current_user_can( 'manage_options' ) ) {
extract( $args );
$fb_output = null;
if ( $container ) { $fb_output = '<' . $container;
if ( $container_id ) $fb_output .= ' id="' . $container_id . '"';
if ( $container_class ) $fb_output .= ' class="' . $container_class . '"';
$fb_output .= '>'; }
$fb_output .= '<ul';
if ( $menu_id ) $fb_output .= ' id="' . $menu_id . '"';
if ( $menu_class ) $fb_output .= ' class="' . $menu_class . '"';
$fb_output .= '>'; $fb_output .= '<li><a href="' . admin_url( 'nav-menus.php' ) . '">Add a menu</a></li>'; $fb_output .= '</ul>';
if ( $container ) $fb_output .= '</' . $container . '>';
echo $fb_output; } }}

Ý nghĩa của các phương thức thì bạn xem comment trong code nhé. Và để xem toàn bộ code của lớp Walker_Nav_Menu, bạn hãy xem code từ dòng 10 đến dòng 191 trong tập tin /wp-includes/nav-menu-template.php của mã nguồn WordPress. Chúng ta sẽ làm việc bằng cách tuỳ biến lại code trong phần này mà không phải đụng vào mã nguồn, bằng cách kế thừa Walker_Nav_Menu cho lớp Chiaseaz_Nav_Walker
Lưu ý là nếu bạn cần tuỳ biến phương thức nào thì khai báo phương thức đó ra thôi, không cần khai báo lại tất cả. Mà đã khai báo rồi thì bạn phải viết code giống như nó đã được định nghĩa sẵn ở /wp-includes/nav-menu-template.php, nếu khai báo sai hoặc thiếu sẽ bị lỗi.

Làm việc với start_lvl()

Như mình nói ở trong code, phương thức start_lvl() được sử dụng để thiết lập lại các thẻ mở đầu của một cấp độ menu mới, tính từ cấp độ menu gốc. Điều này có nghĩa là chúng ta sẽ sử dụng phương thức này để thay đổi lại cấu trúc cái thẻ <ul class="sub-menu"> mặc định của WordPress trong menu con.
Ở file /wp-includes/nav-menu-template.php, từ đoạn 47 đến 50 là nó thiết lập phương thức này như sau:
01
02
03
04
public function start_lvl( &$output, $depth = 0, $args = array() ) {
  $indent = str_repeat("\t", $depth);
  $output .= "\n$indent<ul class=\"sub-menu\">\n";
}
Đoạn trên có nghĩa là nó sẽ lặp lại chuỗi \t tương ứng với giá trị của $depth (nếu menu đó có level là 2 thì nó lặp lại \t 2 lần) rồi đưa vào biến $indent. Trong PHP, ký tự \t nghĩa là một tab (khoảng trắng trước ký tự). Sau đó, nó sẽ sử dụng biến $indent này cho $output để hiển thị ra ngoài với \n$indent\n nghĩa là ký tự đại diện cho một hàng văn bản. Vậy ý nghĩa đoạn trên là nó in <ul class="sub-menu"> thụt vào so với phần tử trước đó, kiểu thế này nè:
01
02
03
04
<li>
 <ul class="sub-menu">
 <li>Level 1</li>
 </ul>
Và cái khoảng trắng đằng trước thẻ <ul> chính là \t đấy.

Thực hành: Thêm nội dung vào trước thẻ <ul class=”sub-menu”>

Thực hành bằng cách thêm một thẻ <span> đằng trước menu con nên sẽ viết code cho phương thức start_lvl() trong lớp Chiaseaz_Nav_Walker:
01
02
03
04
05
06
public function start_lvl( & $output, $depth, $args )
{
  $indent = str_repeat("\t", $depth);
  $output .= "<span class=\"sub-intro\">Menu con</span>";
  $output .= "\n$indent<ul class=\"sub-menu\">\n";
}

Sử dụng phương thức end_lvl()

Phương thức end_lvl() được ứng dụng khá đơn giản, đó là nó sẽ hiển thị phần kết thúc của một cấp menu mới, tức là thẻ </ul>.
Từ đoạn 63 đến 66 trong /wp-includes/nav-menu-template.php nó được khai báo như sau:
01
02
03
04
public function end_lvl( &$output, $depth = 0, $args = array() ) {
  $indent = str_repeat("\t", $depth);
  $output .= "$indent</ul>\n";
}
Ý nghĩa thì cũng tương tự như phần start_lvl() mà thôi, chỉ khác là nó có thêm </ul>.

Sử dụng phương thức start_el()

Phương thức này có lẽ là chúng ta sẽ làm việc nhiều nhất trong Walker_Nav_Menu vì nó sẽ thiết lập lại cấu trúc hiển thị của các phần tử <li> trong menu. Trước khi sử dụng, bạn cần đọc qua code của phương thức này từ dòng 81 đến 173 trong /wp-includes/nav-menu-template.php. Và ở đây mình sẽ giải thích cặn kẽ ý nghĩa các code trong phần này của lớp gốc Walker_Nav_Menu trong tập tin này.
Dòng số 82 ta có biến $indent là để thiết lập các khoảng trắng với ký tự \t để nó hiển thị thẻ <li> cho thụt vào (xem dòng 115).
01
$indent = ( $depth ) ? str_repeat( "\t", $depth ) : '';
Dòng 84 và 85 nghĩa là nó sẽ lấy cái ID của đối tượng trong menu thông qua đối tượng dữ liệu $item->ID và nối vào đối tượng $item->classes để hiển thị các class HTML tượng trưng cho mỗi đối tượng trong menu (ví dụ như class menu-item-83), các class sau khi được nối sẽ được đưa vào biến $classes.
01
02
$classes = empty( $item->classes ) ? array() : (array) $item->classes;
$classes[] = 'menu-item-' . $item->ID;
Kế tiếp ở dòng 98 và 99, sau khi có biến $classes ở trên thì ở hai dòng này nó sẽ tiến hành bóc tách các giá trị trong mảng $classes để hiển thị ra ngoài phần tử menu cách nhau một khoảng trắng. Ở đoạn này, bạn sẽ thấy có dòng apply_filters với hook tên là nav_menu_css_class, sau này bạn cần làm gì liên quan đến sửa lại class trong menu thì cứ sử dụng add_filter mà lọc qua cái hook này.
01
02
$class_names = join( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item, $args, $depth ) );
$class_names = $class_names ? ' class="' . esc_attr( $class_names ) . '"' : '';
Dòng 112 và 113 cũng tương tự với 98 và 99 nhưng nó làm nhiệm vụ thiết lập ID của từng phần tử trong menu.
01
02
$id = apply_filters( 'nav_menu_item_id', 'menu-item-'. $item->ID, $item, $args, $depth );
$id = $id ? ' id="' . esc_attr( $id ) . '"' : '';
Dòng 115 là thiết lập những gì mà nó sẽ hiển thị ra bên ngoài. Ở đây bạn có thể dễ dàng hiểu là nó in một thẻ<li> với ID và Class theo hai biến $class_names và $id ở trên.
01
$output .= $indent . '<li' . $id . $class_names .'>';
Dòng 117 đến dòng 121 nghĩa là nó sẽ đưa một số thuộc tính của mỗi phần tử danh sách trong menu vào mảng $atts để xíu nữa nó sẽ dùng foreach lặp lại với mục đích tách các giá trị ra để hiển thị với thẻ a.
01
02
03
04
05
$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 : '';
Mình ví dụ một đoạn cho bạn nào chưa hiểu, ví dụ đoạn này:
01
$atts['title']  = ! empty( $item->attr_title ) ? $item->attr_title : '';
Nghĩa là nếu $item->attr_title có giá trị (phủ định của rỗng) thì nó sẽ gọi $item->attr_title ra (đối tượng này sẽ trả về nội dung trong thuộc tính Title của menu). Còn ngược lại là không làm gì cả (biểu diễn bằng hai ký tự ' ').
Đoạn 151 đến 156 là nó sẽ hiển thị cấu trúc của phần tử nằm bên trong <li> ra, các phần tử này được chứa trong biến $item_output.
01
02
03
04
05
06
$item_output = $args->before;
$item_output .= '<a'. $attributes .'>';
/** This filter is documented in wp-includes/post-template.php */
$item_output .= $args->link_before . apply_filters( 'the_title', $item->title, $item->ID ) . $args->link_after;
$item_output .= '</a>';
$item_output .= $args->after;

Thực hành: Hiển thị Description của đối tượng menu.

Nếu chúng ta muốn hiển thị thêm phần Description của đối tượng trong menu ra bên ngoài, thì trước tiên ta sẽ khai báo thêm một dòng với biến $item_output, mình sẽ kiểm tra xem nếu có description rồi thì nó mới hiển thị.
01
02
03
04
05
06
07
$item_output = $args->before;
$item_output .= '<a'. $attributes .'>';
/** This filter is documented in wp-includes/post-template.php */
$item_output .= $args->link_before . apply_filters( 'the_title', $item->title, $item->ID ) . $args->link_after;
$item_output .= '</a>';
$item_output .= !empty( $atts['description'] ) ? '<span class="description">' . esc_attr( $atts['description'] ) . '</span>' : '';
$item_output .= $args->after;
Sử dụng phương thức end_el()
Phương thức này cũng y hệt như end_lvl(), để nó hiển thị phần kết thúc của phần tử <li> trong menu.
Từ dòng 187 đến 189 trong  /wp-includes/nav-menu-template.php nó đã thiết lập sẵn thế này:
01
02
03
public function end_el( &$output, $item, $depth = 0, $args = array() ) {
  $output .= "</li>\n";
}

Một số thư viện Walker Nav Menu có sẵn

Dưới đây là danh sách các thư viện walker với nhiều tính năng khác nhau có sẵn mà bạn có thể sử dụng lại trên internet:
  • wp-bootstrap-navwalker – Thư viện walker để hiển thị menu theo cấu trúc của Bootstrap.
  • wp-foundation-walker – Thư viện walker để hiển thị menu theo cấu trúc của Foundation.
  • Clean-Menu-Walker – Thư viện walker để hiển thị menu gọn hơn, xoá các class không cần thiết.

Nhận xét