Ask your WordPress questions! Pay money and get answers fast! (more info)

Dynamic listing of child-categories in twentyten menu?

  • SOLVED

Hi,
I am working on a site where I need to list all the child categories of one category, in the menu, as a dropdown of the parent category's menu item. Adding them manually through the menu editor will not work for long, it must be dynamic.

I have tried adding wp_list_categories(child_of="8"&.....) into the #access in the header template, just behind wp_nav_menu(), but i can not figure out how to make the output work with the twentyten dropdowns, and neither the other way round - how to change the output to what would work.

Ideally, I would also like to place this "inbetween" some of the other menu choices.

Anyone?

Answers (4)

2011-01-18

Maor Barazany answers:

Well, this is a more or less known issue with WordPress nav menu system.
In short: They cannot be dynamic.
The advantage of it is only for letting your user build and change the menu for needs.
If you have a lot of sub categories, you have to use the [[LINK href="http://codex.wordpress.org/Template_Tags/wp_list_categories"]]wp_list_categories()[[/LINK]] function with parameters to auto show child cats.

In general, you have two possible solutions (if you count only code solutions and not want to base on a plugin).

The first one depends on that that the category that has childs is in known position in the menu. Then, you can build the code in general like this -
Create two nav menu and show them in separate and insert another call to list the category between them. Something like this: (See specific functions' parameters in the codex)
<?php
wp_nav_menu('menu' = > '1'); //the first menu
wp_list_categories($your_needed_args); //dynamic builld the category item with its children
wp_nav_menu('menu' => '2'); //the second menu - continuing the menu
?>


This solution is good only if the location of the category (with its childs) is known inside the menu order that needed, and is not needed to be changed by the end user.

Second option, is building yourself a Custom Walker for custom output of the Walker (which created the output for all nav'ing functions such as wp_list_pages, wp_list_categories_, wp_nav_menu ), and insert code that will buld it as you wish.
Here's a [[LINK href="http://erikshosting.com/wordpress-tips-code/building-a-wordpress-walker-creating-custom-dynamic-menu-outputs/"]]post explaining how to create a custom walker[[/LINK]]


Hope it helps


Maor Barazany comments:

Nick had a little mistake in his code, since $args is for the whole menu and not for the item itself.

Take this function instead, based on Nick's code with my changes:


add_action('wp_loaded','register_nav_menu_class');

function register_nav_menu_class(){
class Custom_Walker_Nav_Menu extends Walker_Nav_Menu {

function start_el(&$output, $item, $depth, $args) {
global $wp_query;
$indent = ( $depth ) ? str_repeat( "\t", $depth ) : '';
$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 ) );
$class_names = ' class="' . esc_attr( $class_names ) . '"';

$id = apply_filters( 'nav_menu_item_id', 'menu-item-'. $item->ID, $item, $args );
$id = strlen( $id ) ? ' id="' . esc_attr( $id ) . '"' : '';

$output .= $indent . '<li' . $id . $value . $class_names .'>';

$attributes = ! empty( $item->attr_title ) ? ' title="' . esc_attr( $item->attr_title ) .'"' : '';
$attributes .= ! empty( $item->target ) ? ' target="' . esc_attr( $item->target ) .'"' : '';
$attributes .= ! empty( $item->xfn ) ? ' rel="' . esc_attr( $item->xfn ) .'"' : '';
$attributes .= ! empty( $item->url ) ? ' href="' . esc_attr( $item->url ) .'"' : '';

$item_output = $args->before;
$item_output .= '<a'. $attributes .'>';
$item_output .= $args->link_before . apply_filters( 'the_title', $item->title, $item->ID ) . $args->link_after;
$item_output .= '</a>';

if($item->object == 'category'){
$child_cats = wp_list_categories('title_li=&echo=0&child_of='.$item->object_id);
if( $child_cats ){
$item_output .= '<ul class="sub-menu">' .$child_cats. '</ul>';;
}
}

$item_output .= $args->after;
$output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );

}
}
}


Torstein Opperud comments:

Sorry Maor, the problem is still there, same problem with both yours and Nick's new code :(

(its your code that is in functions.php at the moment)


Maor Barazany comments:

Check again carefully , since I have tested my code in a local site and after making this change, it was fixed in my site. If you want, send me your wp-admin's user and password, and login to your FTP so I could see the functions.php file also and I can check. This code does work, you might have mistaken in something.


Torstein Opperud comments:

Argh,
found it, totally silly mistake (as they often are). Yep, now its working, thanks!

Actually, since both you and Nick contributed a lot here, I think you both deserve the reward, so if you would send me a pm with your paypal-address, I'll make sure you get 50$ each, and I'll add a little bonus for you since you seem to have been first with the bugfix :)

Also - if you are interested: I often have smaller jobs (mostly regarding wordpress, both smaller and larger than this) I need done, so if you use a different address from your paypal-address, please include that, and I'll get in touch when I need something :)

Again, thanks!
Torstein

2011-01-18

Nick Parsons answers:

Here's the code for implementing a custom walker like Mr. Barazany suggested:

add_action('wp_loaded','register_nav_menu_class');


function register_nav_menu_class(){
class Custom_Walker_Nav_Menu extends Walker_Nav_Menu {
function start_el(&$output, $item, $depth, $args) {
if($item->object == 'category'){
$child_cats = wp_list_categories('title_li=&echo=0&child_of='.$item->object_id);
if( $child_cats ){
$args->after .= '<ul class="sub-menu">' .$child_cats. '</ul>';
}
}
global $wp_query;
$indent = ( $depth ) ? str_repeat( "\t", $depth ) : '';

$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 ) );
$class_names = ' class="' . esc_attr( $class_names ) . '"';

$id = apply_filters( 'nav_menu_item_id', 'menu-item-'. $item->ID, $item, $args );
$id = strlen( $id ) ? ' id="' . esc_attr( $id ) . '"' : '';

$output .= $indent . '<li' . $id . $value . $class_names .'>';

$attributes = ! empty( $item->attr_title ) ? ' title="' . esc_attr( $item->attr_title ) .'"' : '';
$attributes .= ! empty( $item->target ) ? ' target="' . esc_attr( $item->target ) .'"' : '';
$attributes .= ! empty( $item->xfn ) ? ' rel="' . esc_attr( $item->xfn ) .'"' : '';
$attributes .= ! empty( $item->url ) ? ' href="' . esc_attr( $item->url ) .'"' : '';

$item_output = $args->before;
$item_output .= '<a'. $attributes .'>';
$item_output .= $args->link_before . apply_filters( 'the_title', $item->title, $item->ID ) . $args->link_after;
$item_output .= '</a>';
$item_output .= $args->after;

$output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
}
}
}


That's all you need to add child categories menu support to 2010, you can now simply use this:
<?php wp_nav_menu( array( 'container_class' => 'menu-header', 'theme_location' => 'primary', 'walker' => new Custom_Walker_Nav_Menu() ) ); ?>
in the header to spit out the menu.


Torstein Opperud comments:

Hi Nick!
I think you are really onto something! There is just one small problem, if you look at http://stikk.opperud.com, your walker tries to add subcategories also on other menu items, such as pages ("about"), and custom links ("Logg ut" and "Skriv nytt innlegg").

If you fix this the cash is yours :)

T.


Nick Parsons comments:

Great, thanks! Here's the new register_nav_menu_class function that will fix the bug you mentioned:
function register_nav_menu_class(){
class Custom_Walker_Nav_Menu extends Walker_Nav_Menu {
function start_el(&$output, $item, $depth, $args) {
global $wp_query;
$indent = ( $depth ) ? str_repeat( "\t", $depth ) : '';

$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 ) );
$class_names = ' class="' . esc_attr( $class_names ) . '"';

$id = apply_filters( 'nav_menu_item_id', 'menu-item-'. $item->ID, $item, $args );
$id = strlen( $id ) ? ' id="' . esc_attr( $id ) . '"' : '';

$output .= $indent . '<li' . $id . $value . $class_names .'>';

$attributes = ! empty( $item->attr_title ) ? ' title="' . esc_attr( $item->attr_title ) .'"' : '';
$attributes .= ! empty( $item->target ) ? ' target="' . esc_attr( $item->target ) .'"' : '';
$attributes .= ! empty( $item->xfn ) ? ' rel="' . esc_attr( $item->xfn ) .'"' : '';
$attributes .= ! empty( $item->url ) ? ' href="' . esc_attr( $item->url ) .'"' : '';

$item_output = $args->before;
$item_output .= '<a'. $attributes .'>';
$item_output .= $args->link_before . apply_filters( 'the_title', $item->title, $item->ID ) . $args->link_after;
$item_output .= '</a>';

if($item->object == 'category'){
$child_cats = wp_list_categories('title_li=&echo=0&hide_empty=0&child_of='.$item->object_id);
if( $child_cats ){
$item_output .= '<ul class="sub-menu">' .$child_cats. '</ul>';
}
}

$item_output .= $args->after;

$output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
}
}
}


Torstein Opperud comments:

Sorry Nick, the problem is still there, both in your new code and in Maor Barazany's code :(


Nick Parsons comments:

Hmm,

When I went to your demo site (http://stikk.opperud.com/) the 'About', 'Logg ut', and 'Skriv nytt innlegg' items no longer have the categories listed under them - isn't that the bug you described?


Torstein Opperud comments:

I found the mistake, it was mine and it was totally silly, fixed it just an hour ago or so :)

But, since both you and Maor contributed a lot here, I think you both deserve the reward, so if you would send me a pm with your paypal-address, I'll make sure you get 50$ each :)

And - if you are interested: I often have smaller jobs (mostly regarding wordpress, both smaller and larger than this) I need done, so if you use a different address from your paypal-address for workrelated mail, please include that, and I'll get in touch when I need something :)

2011-01-18

Andrzej answers:

This can be done easily by replacing default Wordpress nav system by [[LINK href="http://wordpress.org/extend/plugins/menubar/"]]Menubar Plugin[[/LINK]]. If you wish me to implement it for you, pls contact me [[LINK href="http://www.wpquestions.com/user/profile/id/530"]]here[[/LINK]].
Cheers,
Andrzej


Torstein Opperud comments:

I would prefer not to be dependent on yet another plugin, we are already a bit heavy on queries, so I would rather have a snippet of code that does only this one thing.

But it is a possible solution if I dont get anything else, thanks :)

(and to think of how I have searched both wp.org and googled every possible combination of "menu" "categories" and similar words for hours and hours without finding that plugin... argh!!)


Andrzej comments:

It still can be done via wp_nav_menu() if you want stick to it, by some sort of PHP str_replace hacks. I'm availiable now and can fix it for you that way - I just need your website address and ftp details. ([[LINK href="http://wpquestions.com/user/contact/id/530"]]contact link[[/LINK]])


Andrzej comments:

if you do not want to send ftp, pls paste here you code around wp_nav_menu() function and html output of the site (can be done via zipped attachment) or just post a link to the website?

2011-01-18

Nilesh shiragave answers:

Do you want to use the wp_nav_menu() ?

or you can achieve it by using the wp_list_pages() just add following code inside the #access div

<div class="menu">
<ul>
<?php wp_list_pages('title_li=&sort_column=menu_order');?>
<?php wp_list_categories('child_of=8'); ?>
</ul>

</div>