Hiển thị một phần hoặc một nhánh của cây menu bằng wp_nav_menu()
Câu hỏi
Tôi có một menu trong WP Admin trông như thế này:
Tôi muốn hiển thị tất cả các link con trên sidebar bất cứ khi nào tôi đang ở trang gốc (parent page). Ví dụ: nếu người dùng đang ở trang “About Us” (về chúng tôi), tôi muốn danh sách gồm 4 link được đánh dấu bằng mực xanh (trong hình phía trên) xuất hiện trên sidebar.
Tôi đã xem tài liệu hướng dẫn cho wp_nav_menu() và dường như không có cách nào để chỉ định một node cụ thể của trình đơn – bước đầu tiên của việc tạo các liên kết.
Tôi đã có giải pháp cho một tình huống tương tự, tình huống này dựa trên các mối quan hệ được tạo ra bởi trang gốc (page parent). Nhưng tôi đang tìm kiếm một giải pháp chỉ sử dụng hệ thống menu. Rất mong nhận được sự giúp đỡ của mọi người.
jessegavin
Vậy có phải bạn vẫn muốn giữ toàn bộ menu giống như một menu tùy chỉnh, nhưng vừa tạo một custom walker để hiển thị mở rộng dành riêng cho active subtree? Giống như code này, nhưng mở rộng wp_nav_menu thay vì wp_list_pages? Gần đây tôi đã làm cái tương tự như thế này, và tôi sẽ đăng code của tôi nếu đó là những gì bạn đang tìm kiếm. – goldenapples
@goldenapples, đó chính xác là cái mà tôi đang cần. Nếu bạn không phiền, hãy đăng code của bạn trong câu trả lời. Tôi sẽ rất biết ơn bạn. – jessegavin
Tôi thắc mắc tại sao một chức năng hữu ích như vậy lại chưa được tích hợp. Nó rất hữu ích cho bất kì trang web nào sử dụng “CMS”. – hakre
Tôi đang cố gắng tìm giải pháp cho vấn đề trên hay vấn đề tương tự như vậy. Tôi có một giải pháp CSS có thể thay thế ở đây: stackoverflow.com/q/7640837/518169 – hyperknot
Câu trả lời chính xác nhất
Tôi đã nghĩ đến cái này khi đọc câu hỏi của bạn, vì vậy tôi xem lại và đưa ra giải pháp này. Nó không dựa vào trường hợp của bạn nhiều lắm:
add_filter( ‘wp_nav_menu_objects’, ‘submenu_limit’, 10, 2 );
function submenu_limit( $items, $args ) {
if ( empty( $args->submenu ) ) {
return $items;
}$ids = wp_filter_object_list( $items, array( ‘title’ => $args->submenu ), ‘and’, ‘ID’ );
$parent_id = array_pop( $ids );
$children = submenu_get_children_ids( $parent_id, $items );foreach ( $items as $key => $item ) {
if ( ! in_array( $item->ID, $children ) ) {
unset( $items[$key] );
}
}return $items;
}function submenu_get_children_ids( $id, $items ) {
$ids = wp_filter_object_list( $items, array( ‘menu_item_parent’ => $id ), ‘and’, ‘ID’ );
foreach ( $ids as $id ) {
$ids = array_merge( $ids, submenu_get_children_ids( $id, $items ) );
}return $ids;
}
Cách dùng
$args = array(
‘theme_location’ => ‘slug-of-the-menu’, // the one used on register_nav_menus
‘submenu’ => ‘About Us’, // could be used __() for translations
);wp_nav_menu( $args );
Câu trả lời của Rarst
Kĩ thuật rất tốt! Tôi có thể hỏi một số điều có thể liên quan đến cái này được không: Bạn sẽ hiển thị nội dung của các trang menu con (submenu) đã được liệt kê theo mẫu như thế nào?
– daniel.tosaba
@daniel.tosaba bạn sẽ phải phân lớp hoặc sử dụng bộ lọc trong lớp Walker_Nav_Menu. Có quá nhiều vấn đề để bàn luận về menu, hãy hỏi các câu hỏi khác về nó? – Rarst
wordpress.stackexchange.com/questions/62758/… – daniel.tosaba
Đúng là một câu trả lời tuyệt vời. Cảm ơn rất nhiều. Nó thực sự nên là một tùy chọn mặc định trong WordPress. – dotty
Hmm, tôi thực sự gặp phải một vấn đề rất lạ. Tôi có một trang gọi là “Children’s”. Và có vẻ như dấu nháy đơn trong tên gọi này không tìm được trang. Mọi người có ý tưởng gì không? – dotty
@dotty Tôi không nhớ chính xác những gì được lưu trữ trong trường mà code lọc. Nó có thể là phiên bản tiêu đề đã bị loại bỏ. – Rarst
Xin lỗi, tôi không chắc ý bạn là gì. Bạn có thể nói rõ hơn được không? – dotty
@dotty no có thể được lưu trữ dưới dạng Children\’s hoặc tương tự như vậy, tôi không cài code này vì vậy không thể test thử ngay bây giờ được. – Rarst
Có vẻ như nó không còn hoạt động trong WP 4 nữa. Tôi gặp phải lỗi này: Strict Standards: Only variables should be passed by reference in -> on line -> $parent_id = array_pop( wp_filter_object_list( $items, array( ‘title’ => $args->submenu ), ‘and’, ‘ID’ ) ); – gdaniel
@gdaniel Tôi đã test thử. Nó vẫn hoạt động và bản update của WP chẳng có gì khác cả, chỉ cần một tinh chỉnh nhỏ để PHP code phù hợp hơn. – Rarst
Thật kì lạ. Tất cả những menu con dùng hàm đó của tôi đều biến mất ngay sau khi tôi update lên WP 4. Dù sao đi nữa cũng cảm ơn bạn vì đã xem qua. Tôi sẽ kiểm tra xem có cái gì khác có thể đã xảy ra nữa hay không. – gdaniel
Tôi đã tìm ra vấn đề của mình rồi. Tôi đã sử dụng đối số menu => “menu-name” trong wp_nav_menu … Tôi đã xóa ‘menu’ và thêm thêm_location. Và mọi thứ đã trở lại bình thường. – gdaniel
Giải pháp thật tuyệt với, @Rarst! Như mọi khi. – Eric Holmes
Này @Rarst, câu trả lời của bạn vẫn có một số thiếu sót đấy. Phiên bản được chấp nhận thực sự nên được viết lại để giải thích cho nav walker tùy chỉnh. Vì người dùng chỉ có thể lọc “current-menu-item”, “current-menu-parent”, and “current-menu-ancestor” sau đó hiển thị menu phụ. Không có bộ lọc thì sẽ không bị hack. – Imperative Ideas
@ImperativeIdeas Câu trả lời của tôi giúp hiển thị nhánh tùy ý (arbitrary branch) theo tên, không chỉ có tên hiện tại. – Rarst
Rất nhanh gọn. Nếu ai đó quan tâm, để làm được tương tự như vậy nhưng bằng cách dùng page ID, thay đổi wp_filter_object_list line thành wp_filter_object_list( $items, array( ‘object_id’ => $args->submenu ), ‘and’, ‘ID’ ); – Ben
@goldenapples: Walker class của bạn không hoạt động. Nhưng ý tưởng rất hay. Tôi đã tạo ra một walker dựa trên ý tưởng của bạn:
class Selective_Walker extends Walker_Nav_Menu
{
function walk( $elements, $max_depth) {$args = array_slice(func_get_args(), 2);
$output = ”;if ($max_depth < -1) //invalid parameter
return $output;if (empty($elements)) //nothing to walk
return $output;$id_field = $this->db_fields[‘id’];
$parent_field = $this->db_fields[‘parent’];// flat display
if ( -1 == $max_depth ) {
$empty_array = array();
foreach ( $elements as $e )
$this->display_element( $e, $empty_array, 1, 0, $args, $output );
return $output;
}/*
* need to display in hierarchical order
* separate elements into two buckets: top level and children elements
* children_elements is two dimensional array, eg.
* children_elements[10][] contains all sub-elements whose parent is 10.
*/
$top_level_elements = array();
$children_elements = array();
foreach ( $elements as $e) {
if ( 0 == $e->$parent_field )
$top_level_elements[] = $e;
else
$children_elements[ $e->$parent_field ][] = $e;
}/*
* when none of the elements is top level
* assume the first one must be root of the sub elements
*/
if ( empty($top_level_elements) ) {$first = array_slice( $elements, 0, 1 );
$root = $first[0];$top_level_elements = array();
$children_elements = array();
foreach ( $elements as $e) {
if ( $root->$parent_field == $e->$parent_field )
$top_level_elements[] = $e;
else
$children_elements[ $e->$parent_field ][] = $e;
}
}$current_element_markers = array( ‘current-menu-item’, ‘current-menu-parent’, ‘current-menu-ancestor’ ); //added by continent7
foreach ( $top_level_elements as $e ){ //changed by continent7
// descend only on current tree
$descend_test = array_intersect( $current_element_markers, $e->classes );
if ( !empty( $descend_test ) )
$this->display_element( $e, $children_elements, 2, 0, $args, $output );
}/*
* if we are displaying all levels, and remaining children_elements is not empty,
* then we got orphans, which should be displayed regardless
*/
/* removed by continent7
if ( ( $max_depth == 0 ) && count( $children_elements ) > 0 ) {
$empty_array = array();
foreach ( $children_elements as $orphans )
foreach( $orphans as $op )
$this->display_element( $op, $empty_array, 1, 0, $args, $output );
}
*/
return $output;
}
}
Bây giờ bạn có thể dùng:
<?php wp_nav_menu(
array(
‘theme_location’=>’test’,
‘walker’=>new Selective_Walker() )
); ?>
Output là một danh sách bao gồm phần tử gốc hiện tại (current root element) và đó là phần tử con (không phải con của chúng). Def: phần tử gốc: = Mục menu level cao nhất tương ứng với trang hiện tại hoặc là trang gốc của trang hiện tại, hoặc là trang gốc của parent (trang cha mẹ).
Đây không chính xác là câu trả lời cho câu hỏi ban đầu nhưng đây là giải pháp tốt cho top level item. Nó rất có ích đối với tôi, bởi vì tôi muốn các phần tử top level sẽ giống như tiêu đề của sidebar. Nếu bạn muốn loại bỏ nó, bạn có thể phải ghi đề display_element hoặc sử dụng một HTML-Parser.
Câu trả lời của davidn
Xin chào @jessegavin:
Nav Menus được lưu trữ với sự kết hợp của custom post type và custom taxonomy. Mỗi menu được lưu trữ dưới dạng một Term (ví dụ: “About Menu”, có thể tìm thấy trong wp_terms) của phân loại tùy chỉnh (custom Taxonomy) (ví dụ: nav_menu, có thể tìm thấy trong wp_term_taxonomy.)
Mỗi menu item Nav được lưu trữ như một bài post_type == ‘nav_menu_item’ (ví dụ: “About the Firm (Giới thiệu về công ty)”, có thể tìm thấy trong wp_posts) với các thuộc tính được lưu trữ dưới dạng post meta (trong wp_postmeta), sử dụng tiền tố meta_key của _menu_item_ * trong đó _menu_item_menu_item_parent là ID bài đăng gốc (parent) của menu item – Nav Menu item post của bạn.
Mối quan hệ giữa các menu và menu item được lưu trữ trong wp_term_relationships nơi object_id liên hệ với $ post-> ID cho Nav Menu Item và $ term_relationships-> term_taxonomy_id liên hệ với menu được chỉ định chung trong wp_term_taxonomy và wp_terms.
Tôi chắc rằng nó sẽ có thể móc (hook) cả ‘wp_update_nav_menu’ và ‘wp_update_nav_menu_item’ để tạo ra các menu chính xác trong wp_terms và một tập hợp các quan hệ song song trong wp_term_taxonomy và wp_term_relationships, trong đó mỗi Menu Item Nav có các Menu item phụ cũng sẽ trở thành Nav Menu của riêng nó.
Bạn cũng cần móc (hook) ‘wp_get_nav_menus’ (cái mà tôi đề nghị nên được thêm vào WP 3.0 dựa trên một số công việc tương tự mà tôi đã thực hiện một vài tháng trước) để đảm bảo rằng Nav Menus được tạo của bạn không được hiển thị đối với các thao tác bởi người dùng trong admin, nếu không chúng sẽ bị đồng bộ hóa rất nhanh và sau đó bạn sẽ phải đối mặt với một cơn ác mộng dữ liệu.
Nghe có vẻ đây là một dự án thú vị và hữu ích, nhưng còn một chút công việc về code và testing tôi không thể giải quyết ngay bây giờ. Một phần vì đồng bộ hóa dữ liệu có xu hướng là một PITA và không thể tránh khỏi việc mắc lỗi (và bởi vì áp lực từ khách hàng buộc tôi phải hoàn thành công việc. 🙂 Nhưng là một người hiểu rõ những điều đã nói ở trên, tôi khẳng định tôi là một WordPress plugin developer đầy tham vọng và động lực. Và tôi có thể code nó nếu khách hàng muốn.
Tất nhiên, bây giờ bạn phải hiểu rằng nếu bạn code nó, bạn có trách nhiệm phải chia sẻ nó bằng cách đăng lại ở đây để mọi người cùng hưởng! 🙂
Câu trả lời của MikeSchinkel
Đây là một walker mở rộng mà có thể bạn đang tìm kiếm:
class Selective_Walker extends Walker_Nav_Menu
{function walk( $elements, $max_depth) {
$args = array_slice(func_get_args(), 2);
$output = ”;if ($max_depth < -1) //invalid parameter
return $output;if (empty($elements)) //nothing to walk
return $output;$id_field = $this->db_fields[‘id’];
$parent_field = $this->db_fields[‘parent’];// flat display
if ( -1 == $max_depth ) {
$empty_array = array();
foreach ( $elements as $e )
$this->display_element( $e, $empty_array, 1, 0, $args, $output );
return $output;
}/*
* need to display in hierarchical order
* separate elements into two buckets: top level and children elements
* children_elements is two dimensional array, eg.
* children_elements[10][] contains all sub-elements whose parent is 10.
*/
$top_level_elements = array();
$children_elements = array();
foreach ( $elements as $e) {
if ( 0 == $e->$parent_field )
$top_level_elements[] = $e;
else
$children_elements[ $e->$parent_field ][] = $e;
}/*
* when none of the elements is top level
* assume the first one must be root of the sub elements
*/
if ( empty($top_level_elements) ) {$first = array_slice( $elements, 0, 1 );
$root = $first[0];$top_level_elements = array();
$children_elements = array();
foreach ( $elements as $e) {
if ( $root->$parent_field == $e->$parent_field )
$top_level_elements[] = $e;
else
$children_elements[ $e->$parent_field ][] = $e;
}
}$current_element_markers = array( ‘current-menu-item’, ‘current-menu-parent’, ‘current-menu-ancestor’ );
foreach ( $top_level_elements as $e ) {
// descend only on current tree
$descend_test = array_intersect( $current_element_markers, $e->classes );
if ( empty( $descend_test ) ) unset ( $children_elements );$this->display_element( $e, $children_elements, $max_depth, 0, $args, $output );
}/*
* if we are displaying all levels, and remaining children_elements is not empty,
* then we got orphans, which should be displayed regardless
*/
if ( ( $max_depth == 0 ) && count( $children_elements ) > 0 ) {
$empty_array = array();
foreach ( $children_elements as $orphans )
foreach( $orphans as $op )
$this->display_element( $op, $empty_array, 1, 0, $args, $output );
}return $output;
}}
Dựa trên code mfields’ tôi đã tham chiếu trong bình luận của tôi trước đó. Công việc của nó là check khi walking menu để xem liệu các yếu tố hiện tại là (1) mục trình đơn hiện tại (current menu item), hay (2) một gốc (ancestor) của mục trình đơn hiện tại (current menu item), và mở rộng subtree (nhánh con) bên dưới nó chỉ khi một trong những điều kiện trên được thỏa mãn. Hy vọng nó sẽ giúp ích cho bạn.
Để sử dụng nó, chỉ cần thêm đối số “walker” khi bạn gọi menu, ví dụ:
<?php wp_nav_menu(
array(
‘theme_location’=>’test’,
‘walker’=>new Selective_Walker() )
); ?>
Câu trả lời của goldenapples