Tuỳ biến cấu trúc menu WordPress với Walker_Nav_Menu

15 Tháng Sáu, 2015

Khi chúng ta tiến hành tự thêm menu vào theme trong WordPress bằng hàm register_nav_menu và cho hiển thị bằng hàm wp_nav_menu thì nó sẽ hiển thị ra với một cấu trúc mặc định trong WordPress kiểu như các menu con sẽ nằm trong một thẻ <ul> với class sub-menu, hoặc mỗi thẻ <li> trong menu sẽ có các class nhất định.

Nhưng giả sử dự án bạn cần làm có một cấu trúc menu khác (điển hình là cấu trúc menu của Bootstrap hoặc Foundation) thì dĩ nhiên bạn sẽ cần phải sửa cấu trúc menu của WordPress lại.

Trong bài viết này, mình sẽ giải thích cho bạn về lớp Walker_Nav_Menu vốn được sử dụng trong WordPress để thiết lập cấu trúc hiển thị của menu. Nếu chúng ta tạo ra một lớp khác kề thừa lại lớp này thì chúng ta có thể tự thiết lập lại cấu trúc của các menu chỉ định.

Hiểu trước khi làm:

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. Trong bài này ta chỉ sử dụng Walker trong việc sửa cấu trúc menu.

Để 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.

  $cddos_walker = new cddos_Nav_Walker; wp_nav_menu( array( 'theme_location' => 'primary', 'menu_class' => 'nav-menu', 'menu_id' => 'primary-menu', 'walker' => $cddos_walker  ) );

Hai đoạn mình in đậm nghĩa là trước tiên mình tạo ra một đối tượng mới tên là $cddos_walker từ lớpcddos_Nav_Walker của mình, sau đó mình sẽ sử dụng đối tượng này vào tham số walker của hàm wp_nav_menu.

Vậy cái cddos_Nav_Walker ở đâu ra? Tạo ra chứ ở đâu nữa :D, bây giờ chúng ta sẽ tạo ra nó 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 cddos_Nav_Walker extends Walker_Nav_Menu {

/** * Phương thức start_lvl() * Được sử dụng để hiển thị các thẻ bắt đầu cấu trúc của một cấp độ mới trong menu. (ví dụ: <ul class=”sub-menu”>) * @param string $output | Sử dụng để thêm nội dung vào những gì hiển thị ra bên ngoài * @param interger $depth | Cấp độ hiện tại của menu. Cấp độ 0 là lớn nhất. * @param array $args | Các tham số trong hàm wp_nav_menu() **/
public function start_lvl( &$output, $depth = 0, $args = array() ) {

}

/** * Phương thức end_lvl() * Được sử dụng để hiển thị đoạn kết thúc của một cấp độ mới trong menu. (ví dụ: </ul> ) * @param string $output | Sử dụng để thêm nội dung vào những gì hiển thị ra bên ngoài * @param interger $depth | Cấp độ hiện tại của menu. Cấp độ 0 là lớn nhất. * @param array $args | Các tham số trong hàm wp_nav_menu() **/
public function end_lvl( &$output, $depth = 0, $args = array() ) {

}

/** * Phương thức start_el() * Được sử dụng để hiển thị đoạn bắt đầu của một phần tử trong menu. (ví dụ: <li id=”menu-item-5&quot ;> ) * @param string $output | Sử dụng để thêm nội dung vào những gì hiển thị ra bên ngoài * @param string $item | Dữ liệu của các phần tử trong menu * @param interger $depth | Cấp độ hiện tại của menu. Cấp độ 0 là lớn nhất. * @param array $args | Các tham số trong hàm wp_nav_menu() * @param interger $id | ID của phần tử hiện tại **/
public function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {

}

/** * Phương thức end_el() * Được sử dụng để hiển thị đoạn kết thúc của một phần tử trong menu. (ví dụ: </li> ) * @param string $output | Sử dụng để thêm nội dung vào những gì hiển thị ra bên ngoài * @param string $item | Dữ liệu của các phần tử trong menu * @param interger $depth | Cấp độ hiện tại của menu. Cấp độ 0 là lớn nhất. * @param array $args | Các tham số trong hàm wp_nav_menu() * @param interger $id | ID của phần tử hiện tại **/
public function end_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {

}
}

Ý 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 cddos_Nav_Walker của mình.

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:

  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è:

 <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”>

Ở bài này, chúng ta sẽ 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 cddos_Nav_Walker như sau:

  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:

  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).

$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.

  $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.

  $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.

  $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 $id ở trên.

 $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.

  $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:

$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.

  $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ị.

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

Kết quả:

nav_walker_description

 	public 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 . ‘”‘; }
}
$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;
$output .= apply_filters( ‘walker_nav_menu_start_el’, $item_output, $item, $depth, $args ); }

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:

  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.

Lời kết

Trong bài này có lẽ mình đã viết hơi dài và giải thích quá kỹ so với quy định, nhưng giải thích kỹ như vậy cũng sẽ giúp bạn phần nào hiểu tốt hơn, tránh những thắc mắc hoặc vấn đề dễ gây hiểu lầm. Do vậy, nếu bạn đọc xong mà vẫn không hiểu thì hãy xem video ở đầu bài nhé.