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:
Thực hành: Thêm nội dung vào trước thẻ <ul class=”sub-menu”>
Sử dụng phương thức start_el()
Thực hành: Hiển thị Description của đối tượng 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" ;> ) * @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 ) {
}
}
// end cddos_Nav_Walker
Ý 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.
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.
Ở 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”;
}
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>
.
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
và $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;
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ả:
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 ); }
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”;
}
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:
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é.