#7 – WPDev – Thay đổi cấu trúc Menu WordPress – Walker_Nav_Menu

0 15

Bài học trước chúng ta đã đăng ký vị trí menu và hiển thị được menu ra giao diện. Tuy nhiên, menu hiển thị ra chỉ hiển thị theo định dạng mặc định của WordPress, chưa đúng với template HTMl của chúng ta đã chuẩn bị. Trong bài học này, chúng ta sẽ tiến hành thay đổi cấu trúc Menu WordPress – Walker_Nav_Menu.

Video Thay đổi cấu trúc Menu WordPress – Walker_Nav_Menu

Hướng dẫn chi tiết

Phân tích menu WordPress có định dạng mặc định như thế nào?

Nếu sử dụng menu 1 cấp thì vẫn ổn. Tuy nhiên, khi chúng ta sử dụng menu nhiều cấp thì sẽ có lỗi xảy ra.

Không hỗ tor75 định dạng cho menu đa cấp

Khi chúng ta xem nguồn trang, các bạn sẽ thấy WordPress đã tự động thêm vào những class mặc định của nó cho menu và các menu item.

Menu WordPress chứa các class mặc định trong thẻ ul, li, a - Thay đổi cấu trúc Menu WordPress - Walker_Nav_Menu
Menu WordPress chứa các class mặc định trong thẻ ul, li, a

Các thẻ ul được thêm class menusubmenu. Các thẻ li được thêm 1 số class như menu-item

Trong khi đó, template HTML Cultura của chúng ta như thế này:

Các class của template chúng ta sử dụng hoàn toàn khác - Thay đổi cấu trúc Menu WordPress - Walker_Nav_Menu
Các class của template chúng ta sử dụng hoàn toàn khác

Bạn có thể nhìn thấy sự khác biệt về các class của thẻ ul và li. Do đó, css của chúng ta không hỗ trợ cho mennu mặc định của WordPress, menu đa cấp không hoạt động như mong muốn.

Ngoài ra, đối với những link đang được active (trang đang xem), menu item sẽ đổi màu. Hiện tại menu của WordPress vẫn chưa đổi màu.

Vài phút cho quảng cáo Hosting

Các bạn cần hosting PHP - WordPress nhanh, rẻ và dễ sử dụng có thể chọn Azdigi nhé.
Link đăng ký: https://my.azdigi.com/aff.php?aff=1612
Nếu các bạn đăng ký và sử dụng hosting, Góc Làm Web sẽ có một ít tiền để duy trì.

2 cách để menu hoạt động đúng như mong muốn

  1. Code css theo các class chuẩn của WordPress, tên các class, thẻ ul, li, a phải đúng như menu mặc định. Bạn có thể tham khảo theme Gola khi mình đã code css theo WordPress. Demo
  2. Thay đổi cấu trúc của menu theo html và css của chúng ta, xóa (có thể giữ lại nếu muốn) những class mặc định, thay vào đó là các class tự định dạng.

Mình sẽ chọn cách thứ hai, tự ra một cấu trúc menu hoàn toàn khác với mặc định của WordPress. Việc chúng ta cần làm là:

  • Thay đổi các classid của các menu item (bao gồm thẻ ul, li, và thẻ a).
  • Mặc định WordPress sẽ thêm class current-menu-item cho trang đang hoạt động. Chúng ta sẽ thay thế class current-menu-item bằng class active (css chúng ta code theo class active).

Ý tưởng triển khai

Chúng ta sẽ phân rã menu thành từng phần nhỏ, chuẩn bị các class, id, và attribute sẽ dùng. Sau đó, ráp các thành phần lại với nhau và hiển thị kết quả.

  • Phân rã menu mặc định của WordPress
  • Giữ lại những thông tin chính, loại bỏ các thông tin không dùng đến như thẻ html, class mặc định
  • Ráp lại menu theo cấu trúc html mới (từ template có sẵn), đưa dữ liệu động, những thông tin chính được giữ lại từ menu mặc định.
  • Hiển thị ra bên ngoài.

Chúng ta sẽ bóc tách như sau:

  • $output là biến chứa kết quả cuối cùng, và ráp kết quả lại
  • Phần bao bên ngoài là thẻ ul cần chỉnh lại class
  • Mỗi menu item sẽ thay đổi class cho thẻ li và a
  • Đóng thẻ ul và in ra menu hoàn thiện

Để tạo ra menu tùy biến, chúng ta sẽ sử dụng lớp Walker_Nav_Menu. Kế thừa lại lớp này và ghi đè những phương thức bên trong nó.

Link tài liệu trên Codex WordPress cho bạn cần tìm hiểu về lớp Walker_Nav_Menu

Một số phương thức cần viết lại khi kế thừa lớp Walker_Nav_Menu

  • start_lvl : mở đầu của menu, chúng ta sẽ viết lại thẻ ul bao ngoài menu trong phương thức này
  • start_el: phần mở đầu của các menu item (từng thẻ li , a trong menu). Đây sẽ là nơi chúng ta thay đổi các class, id và attribute (title, href …) mặc định của WordPress.
  • end_el: chúng ta chỉ dùng nó để đóng thẻ a và li. Nếu bạn đã đóng thẻ ở start_el thì không cần end_el cũng được.
  • end_lvl: đóng thẻ ul bao ngoài cùng.
  • display_element: một số trường hợp bạn cần viết lại
  • fallback: nếu wp_nav_menu của bạn có fallback thì các bạn cần viết lại phương thức này.
  • $output là tham chiếu được truyền qua lần lượt các phương thức để lưu lại kết quả xử lý.

Đây là mở nguồn của một dev nào đó đã hoàn thành về custom menu. Các bạn có thể tham khảo Github.

Mình sẽ sử dụng code trên Github đó nhé. Tất nhiên là chúng ta cần chỉnh sửa lại để phù hợp với html của chúng ta chứ không phải copy nguyên xi rồi chạy được.

Mình không dùng fallback khi gọi hàm wp_nav_menu trong header.php nên sẽ bỏ qua không ghi đè phương thức fallback. Hơn nữa, code Github thêm class active cho li nhưng HTML của chúng ta sẽ thêm active cho thẻ a bên trong (lưu ý điểm này). Bắt đầu nào

Tạo class GLW_Custom_Nav_Walker kế thừa Walker_Nav_Menu

Trong thư mục includes, các bạn tạo file class-glw-custom-nav-walker.php. Nhớ include vào file functions.php nhé.

Phương thức start_lvl

public function start_lvl( &$output, $depth = 0, $args = array() ) {
	$output .= '<ul class="dropdown-menu">';
}

$output là tham chiếu để giữ lại kết quả sau cùng do chúng ta không return gì. Toán tử .= dùng để nối chuỗi, $output là 1 đoạn string chứa mã html của toàn bộ menu của chúng ta. Qua các phương thức, chuỗi này sẽ được cộng dồn, gắn thêm các thành phần.

Phương thức start_el

public function start_el(&$output , $item , $depth = 0, $args = array() , $id = 0){
        /* class="menu-item menu-item-type-custom 
         menu-item-object-custom current-menu-item 
        current_page_item menu-item-home menu-item-6" */
        $class_li = $class_a = '';
        // lấy các class tự sinh ra của WordPress
        // $default_wp_class_li
        $arr_class_li = empty($item->classes) ? array() : (array) $item->classes;
        // add class định danh riêng menu item
        array_push($arr_class_li, 'menu-item-'.$item->ID);
        // chuyển từ mảng sang chuỗi
        /* $class_li = implode(' ',apply_filters('nav_menu_css_class', 
                         array_filter($arr_class_li), $item, $args)); */
        $class_li .= ' menu-item-'.$item->ID.' ';
        // kiểm tra có menu con thì thêm class
        if($args->has_children && $depth === 0){
            $class_li .= ' dropdown ';
        }elseif($args->has_children && $depth > 0){
            $class_li .= ' dropdown-submenu ';
        }
        // kiểm tra link có active
        if(in_array('current-menu-item', $arr_class_li)){
            $class_a .= ' active ';
        }
        /* menu-item menu-item-type-custom 
        menu-item-object-custom current-menu-item 
        current_page_item menu-item-home menu-item-6 */
        $class_li = !empty($class_li) ? ' class="'.esc_attr($class_li) .'" ':'';
        $class_a = !empty($class_a) ? ' class="'.esc_attr($class_a) .'" ':'';

        // gán id cho các menu item thẻ li       
        $id_li = apply_filters('nav_menu_item_id','menu-item-'.$item->ID, $item, $args);
        $id_li = !empty($id_li) ? ' id="'.esc_attr($id_li).'" ':'';

        // ghép nối thẻ li
        $output .= '<li'.$id_li.$class_li.'>';

        // chỉnh sửa thẻ a
        $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 ) {
        if ( $args->has_children ) {
            $atts['href']   		= '#';
            $atts['data-toggle']	= 'dropdown';
            $atts['class']			= 'dropdown-toggle';
        } else {
            $atts['href'] = ! empty( $item->url ) ? $item->url : '';
        }

        $atts = apply_filters( 'nav_menu_link_attributes', $atts, $item, $args );

        $attr_a = '';
        foreach ( $atts as $attr => $value ) {
            if ( ! empty( $value ) ) {
                $value = ( 'href' === $attr ) ? esc_url( $value ) : esc_attr( $value );
                $attr_a .= ' ' . $attr . '="' . $value . '"';
            }
        }
        // ráp tất cả các thành phần thẻ a lại với nhau
        $item_output = $args->before;
        $item_output .= '<a'.$attr_a.' '.$class_a.'>';
        $item_output .=  $args->link_before.apply_filters('the_title',$item->title,$item->ID)
                          .$args->link_after;
        $item_output .= ($args->has_children && $depth === 0) ? ' ' : '';
        $item_output .= $args->after;
        $output .= apply_filters('walker_nav_menu_start_el', $item_output , $item, $depth, $args);
    }
}

Phương thức khá dài, mình có để comment trong code ở từng đoạn để các bạn theo dõi nhé. Các thao tác từ trên xuống là:

  • Tạo 2 biến kiểu string để lưu lại các class chúng ta sẽ thêm vào cho thẻ li và a
  • Lấy các class tự sinh của WordPress nếu các bạn muốn sử dụng đồng thời các class mặc định và các class tùy chỉnh. Nếu muốn bỏ luôn class mặc định thì bỏ qua bước này nhé.
  • Thêm class đặc trưng. Các bạn nên thêm cho mỗi thẻ li một class có dạng menu-item-id của menu item để người dùng có thể code css cho từng thẻ li nếu họ cần.
  • Kiểm tra xem menu item hiện tại có phải là current-menu-item hay không (kiểm tra item này có phải là trang đang xem). Nếu đúng, chúng ta thay vì thêm class current-menu-item, chúng ta sẽ thêm class active. (Hiện tai thì lưu tạm vào biến $class_a nhé).
  • Bạn echo hay var_dump biến $class_li và $class_a sẽ thấy chúng có dạng là class1 class2 class3. Chúng ta sẽ chuyển thành class=”class1 class2 class3″ . Bước này cho tiện về sau thui chứ bạn thêm class=”” ở bước sau cũng được, tùy nha.
  • Tạo id cho thẻ li theo dạng menu-item-ID item. Gắn 1 hook filter vào đây theo chuẩn của WordPress để những lập trình viên khác, những plugin khác có thể tùy chỉnh menu của bạn theo filter này.
  • Thẻ li chúng ta đã xong, ráp lại bằng cách nối chuỗi theo dạng <li $class_li $id_li >. Việc đóng thẻ li chúng ta làm ở phương thức end_el nhé.
  • Chỉnh sửa các attritbute của thẻ a (href, title, class …).
  • Kiểm tra xong nếu menu item có chứa menu con bên trong thì sẽ thêm 1 số attribute và class khác. Bạn cũng nên gắn 1 hook filter vào đây để plugin khác có thể chỉnh sửa menu của bạn. Giúp theme của bạn tương thích với nhiều plugins hơn, tùy biến cao hơn.
  • Và cuối cùng chúng ta ráp nối tất cả lại với nhau, lưu giá trị vào tham chiếu $output.

Phương thức end_el và end_lvl

Chúng ta chỉ dùng phương thức này để đóng tag.

public function end_el(&$output, $item, $depth = 0, $args = array(), $id = 0){
    $output .= '</li>';
}
public function end_lvl(&$output, $depth = 0, $args = array() ){
    $output .= '</ul>';
}

Phương thức display_element và fallback mình không thay đổi gì. Các bạn cứ copy y chan Github.

Gọi lại menu của chúng ta trong header.php

if(has_nav_menu( 'glw_primary_menu' )){
   wp_nav_menu(array(
        'theme_location'    => 'glw_primary_menu',
        'container'         => false,
        'menu_class'   => 'navbar-nav dropdown',
        'fallback_cb'      => false,
        'depth'            => 5,
        'menu_class'        => 'navbar-nav dropdown',
        'walker'            => new GLW_Custom_Nav_Walker()
   ));
}

Bạn cũng cần viết lại phương thức display_element nhé.

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

Phương thức fallback mình sẽ cần đến vì khi gọi hàm wp_nav_menu ở header, mình đã set fallback_cb là false.

Gọi walker mà chúng ta đã tạo ra nhé. Và bây giờ bạn có thể chạy thử xem sao.

Menu đã hỗ trợ đa cấp và thể hiện link active.

Tài liệu WordPress

Link tài liệu chính chủ WordPress về các vấn đề trong bài học nếu muốn tìm hiểu sâu hơn

Code hoàn thành của bài học: Google Drive

Nếu có thắc mắc, đặt câu hỏi bằng cách comment bên dưới, qua email, hoặc nhắn tin qua Fanpage Góc làm web.

Đừng quên LikeShare nếu thấy bài viết thú vị.

Liên hệ

Để lại một trả lời

Địa chỉ email của bạn sẽ không được công bố.