Displaying Categories Dynamically with UberMenu: Writing an auto-population shortcode

Please note this functionality is now built in to UberMenu 3, with the new Dynamic Content feature.

One thing the WordPress 3 Menu System lacks is the ability to dynamically insert/auto-populate content such as Categories. However, with UberMenu – Responsive WordPress Mega Menu Plugin you can use the menu’s Content Override / Shortcode capability to insert any content you want to into the menu.

One use case is the need to place several columns of categories, stacked top to bottom and then left to right. We could do this with the standard WordPress Menu System and UberMenu by creating third level menu items for each category. The main issue with this is ease of maintenance, which can become tedious when dealing with large numbers of menu items that may change frequently.

Our goal is to:

  1. create a shortcode that will dynamically generate several columns of categories
  2. automatically optimize the number of categories per column to keep them as level as possible
  3. make the shortcode flexible to be able to return specific terms from any taxonomy

The strategy will be to create a wrapper shortcode for the WordPress core get_terms() function, and then output the results in several lists, which we will turn into a column layout with some simple CSS.

Download the PHP & CSS

Part 1: The Shortcode

Shortcode Skeleton

First, we’re going to define a shortcode, menu-terms, in our child theme’s functions.php, along with its accompanying function.

function ss_menu_terms( $atts ){

}
add_shortcode( 'menu-terms' , 'ss_menu_terms' );

The rest of the PHP will go inside the ss_menu_terms function, which will take the arguments passed to the shortcode and return the requested terms in the proper number of columns.

Define Arguments

Next we’re going to define the arguments for the shortcode, along with intelligent defaults, to minimize the amount of arguments that need to be passed to the shortcode. We’re going to define a few output-specific arguments, plus the arguments taken by the get_terms function, to make our shortcode as flexible as possible.

After that we’ll also convert our arguments into proper data types: strings into booleans and arrays.

	extract( shortcode_atts( array(
		'tax' 			=> 'category',
		'display_count'	=> 'false',
		'columns'		=> 5,

		'orderby'		=> 'name', 
		'order'			=> 'ASC',
		'hide_empty'	=> 'false', 
		'exclude'		=> array(), 
		'exclude_tree'	=> array(), 
		'include'		=> array(),
		'number'		=> '', 
		//'fields'		=> 'all', 
		'slug'			=> '', 
		'parent'		=> '',
		'hierarchical'	=> 'true', 
		'child_of'		=> 0, 
		'get'			=> '', 
		'name__like'	=> '',
		'pad_counts'	=> 'false', 
		'offset'		=> '', 
		'search'		=> '', 
		'cache_domain'	=> 'core'
	), $atts ) );

	$display_count = $display_count == 'true' ? true : false;
	$hide_empty = $hide_empty == 'true' ? true : false;
	$hierarchical = $hierarchical == 'false' ? false : true;
	$pad_counts = $pad_counts == 'true' ? true : false;

	if( strpos( $tax , ',' ) ) $tax = explode( ',' , $tax );
	if( !is_array( $exclude ) ) $exclude = explode( ',' , $exclude );
	if( !is_array( $exclude_tree ) ) $exclude_tree = explode( ',' , $exclude_tree );
	if( !is_array( $include ) ) $include = explode( ',' , $include );

The tax argument will define our target taxonomy and allow the plugin to work categories, tags, or any custom taxonomy

The display_count argument will allow us to display a count of the number of posts associated with a term, if we choose.

The columns argument will define the number of columns to evenly divide the terms between.

The rest of the arguments are specific to the get_terms function, and are all defined here. Since we can’t pass arrays or booleans via a shortcode, we’ll use comma-delimited strings and boolean text strings instead.

Get the Terms

Now that we have all of our arguments, we’ll compress them into an array and pass them to the get_terms function to get our list of $term objects.

	$term_args = compact( 
			'orderby' , 'order' , 'hide_empty' , 'exclude' , 
			'exclude_tree' , 'include' , 'number' , 'slug' , 
			'parent' , 'hierarchical' , 'child_of' , 'get' ,
			'name__like' ,  'pad_counts' , 'offset' , 
			'search' , 'cache_domain' );

	$terms = get_terms( $tax , $term_args );

Calculate Columns

Next, we figure out how many columns we need by dividing the number of total terms by the number of desired columns and rounding up.

$n = count( $terms );
$per_column = ceil( $n / $columns );

Now that we know how many terms per column we need, we just start a new list every $per_column items.

Output the terms

Finally, we loop through the terms, creating out lists and list items, and wrapping the term names in links to the term archive pages. We create an output string and return that, because shortcodes must always return their values and not print them directly.

$html = '<div class="menu-terms menu-terms-col-'.$columns.'">';

$html.= '<ul class="menu-terms-column">';

foreach( $terms as $i => $term ){
	$html.= '<li class="term-'.$term->term_id.' term-slug-'.$term->slug.' term-taxonomy-'.$term->taxonomy.'">';
	$html.= '<a href="'.get_term_link($term).'">';
	$html.= $term->name;
	if( $display_count ) $html.= ' ('.$term->count. ')';
	$html.= '</a></li>';

	if( ($i+1) % $per_column == 0 ){
		$html.= '</ul><ul class="menu-terms-column">';
	}
}

$html.= '</ul></div>';

return $html;

Our final shortcode definition looks like this:

function ss_menu_terms( $atts ){

	extract( shortcode_atts( array(
		'tax' 			=> 'category',
		'display_count'	=> 'false',
		'columns'		=> 5,

		'orderby'		=> 'name', 
		'order'			=> 'ASC',
		'hide_empty'	=> 'false', 
		'exclude'		=> array(), 
		'exclude_tree'	=> array(), 
		'include'		=> array(),
		'number'		=> '', 
		//'fields'		=> 'all', 
		'slug'			=> '', 
		'parent'		=> '',
		'hierarchical'	=> 'true', 
		'child_of'		=> 0, 
		'get'			=> '', 
		'name__like'	=> '',
		'pad_counts'	=> 'false', 
		'offset'		=> '', 
		'search'		=> '', 
		'cache_domain'	=> 'core'
	), $atts ) );

	$display_count = $display_count == 'true' ? true : false;
	$hide_empty = $hide_empty == 'true' ? true : false;
	$hierarchical = $hierarchical == 'false' ? false : true;
	$pad_counts = $pad_counts == 'true' ? true : false;

	if( strpos( $tax , ',' ) ) $tax = explode( ',' , $tax );
	if( !is_array( $exclude ) ) $exclude = explode( ',' , $exclude );
	if( !is_array( $exclude_tree ) ) $exclude_tree = explode( ',' , $exclude_tree );
	if( !is_array( $include ) ) $include = explode( ',' , $include );

	$term_args = compact( 
			'orderby' , 'order' , 'hide_empty' , 'exclude' , 
			'exclude_tree' , 'include' , 'number' , 'slug' , 
			'parent' , 'hierarchical' , 'child_of' , 'get' ,
			'name__like' ,  'pad_counts' , 'offset' , 
			'search' , 'cache_domain' );

	$terms = get_terms( $tax , $term_args );

	$n = count( $terms );
	$per_column = ceil( $n / $columns );

	$html = '<div class="menu-terms menu-terms-col-'.$columns.'">';

	$html.= '<ul class="menu-terms-column">';

	foreach( $terms as $i => $term ){
		$html.= '<li class="term-'.$term->term_id.' term-slug-'.$term->slug.' term-taxonomy-'.$term->taxonomy.'">';
		$html.= '<a href="'.get_term_link($term).'">';
		$html.= $term->name;
		if( $display_count ) $html.= ' ('.$term->count. ')';
		$html.= '</a></li>';

		if( ($i+1) % $per_column == 0 ){
			$html.= '</ul><ul class="menu-terms-column">';
		}
	}

	$html.= '</ul></div>';

	return $html;
}
add_shortcode( 'menu-terms' , 'ss_menu_terms' );

Part 2: The CSS

Now we have our markup – by default, 5 lists with the terms evenly distributed among them. But right now they’re just unstyled lists. Adding CSS will turn them into the columns we want.

First, we’re going to define a class, menu-terms-item which we will add to our menu item to ensure it takes up the whole with of the submenu; otherwise our percentage-based columns won’t spread out properly.

#megaMenu ul.megaMenu .menu-terms-item{
	width:100%;
	-webkit-box-sizing:border-box;
	-mox-box-sizing:border-box;
	-o-box-sizing:border-box;
	box-sizing:border-box;
}

Next, we’ll define the layout of the columns themselves, as well as a simple grid to define the percentage widths of the columns from 1 to 8.

#megaMenu ul.megaMenu .menu-terms{
	margin-top:10px;
}
#megaMenu ul.megaMenu .menu-terms a{
	text-decoration: none;
}
#megaMenu ul.megaMenu .menu-terms ul.menu-terms-column{
	-webkit-box-sizing:border-box;
	-mox-box-sizing:border-box;
	-o-box-sizing:border-box;
	box-sizing:border-box;
	float:left;
	margin:0;
	padding-right:20px;
	margin-bottom:20px;
	list-style:none;
}
#megaMenu ul.megaMenu .menu-terms.menu-terms-col-1 ul.menu-terms-column{ width: 100%; }
#megaMenu ul.megaMenu .menu-terms.menu-terms-col-2 ul.menu-terms-column{ width: 50%; }
#megaMenu ul.megaMenu .menu-terms.menu-terms-col-3 ul.menu-terms-column{ width: 33%; }
#megaMenu ul.megaMenu .menu-terms.menu-terms-col-4 ul.menu-terms-column{ width: 25%; }
#megaMenu ul.megaMenu .menu-terms.menu-terms-col-5 ul.menu-terms-column{ width: 20%; }
#megaMenu ul.megaMenu .menu-terms.menu-terms-col-6 ul.menu-terms-column{ width: 16%; }
#megaMenu ul.megaMenu .menu-terms.menu-terms-col-7 ul.menu-terms-column{ width: 14%; }
#megaMenu ul.megaMenu .menu-terms.menu-terms-col-8 ul.menu-terms-column{ width: 12%; }

Finally, below 768 pixels we will collapse the terms into two columns, and below 480px we will collapse them into a single column:

@media only screen and (max-width:767px) {
	#megaMenu ul.megaMenu .menu-terms-item{ width:100% !important; }
	#megaMenu ul.megaMenu .menu-terms.menu-terms-col-1 ul.menu-terms-column,
	#megaMenu ul.megaMenu .menu-terms.menu-terms-col-2 ul.menu-terms-column,
	#megaMenu ul.megaMenu .menu-terms.menu-terms-col-3 ul.menu-terms-column,
	#megaMenu ul.megaMenu .menu-terms.menu-terms-col-4 ul.menu-terms-column,
	#megaMenu ul.megaMenu .menu-terms.menu-terms-col-5 ul.menu-terms-column,
	#megaMenu ul.megaMenu .menu-terms.menu-terms-col-6 ul.menu-terms-column,
	#megaMenu ul.megaMenu .menu-terms.menu-terms-col-7 ul.menu-terms-column,
	#megaMenu ul.megaMenu .menu-terms.menu-terms-col-8 ul.menu-terms-column{ width: 50%; }
}
@media only screen and (max-width:479px) {
	#megaMenu ul.megaMenu .menu-terms.menu-terms-col-1 ul.menu-terms-column,
	#megaMenu ul.megaMenu .menu-terms.menu-terms-col-2 ul.menu-terms-column,
	#megaMenu ul.megaMenu .menu-terms.menu-terms-col-3 ul.menu-terms-column,
	#megaMenu ul.megaMenu .menu-terms.menu-terms-col-4 ul.menu-terms-column,
	#megaMenu ul.megaMenu .menu-terms.menu-terms-col-5 ul.menu-terms-column,
	#megaMenu ul.megaMenu .menu-terms.menu-terms-col-6 ul.menu-terms-column,
	#megaMenu ul.megaMenu .menu-terms.menu-terms-col-7 ul.menu-terms-column,
	#megaMenu ul.megaMenu .menu-terms.menu-terms-col-8 ul.menu-terms-column{ width: 100%; }
}

See also Adding Custom CSS

Part 3: Adding the Shortcode to the menu

Now that we have all our code in place, we’ll add it to our menu. The intention is to auto-populate a full row of leveled columns with a single menu item / shortcode.

First, make sure you have Content Overrides Enabled

Next, create a new second level menu item. I recommend using a “Link” menu item and setting the URL to # and checking the “Disable Link” option. However, you can use any type you like.

If you don’t want to display the link’s Navigation Title above the columns of categories, check the “Disable Text” option.

Next, add the menu-terms-item class to your menu item

Finally, insert the shortcode itself into the Custom Content box.

[menu-terms]

Here’s the result:

The categories are sorted alphabetically by default.

Part 4: Using the shortcode arguments

We can refine the terms returned and their layout by passing arguments to the shortcode. Here are some examples:

Columns

By default, 5 columns to be displayed. To display 6, we would use

[menu-terms columns="6"]

Sorting

By default, the terms will be ordered alphabetically. To order them by ID, you would use

[menu-terms orderby="id"]

Hierarchies

With no arguments passed, the shortcode will return all terms in the taxonomy (by default, this means all Categories). If we want to restrict the set to children of a specific menu item, we can do this:

[menu-terms child_of="5"]

Where 5 is the ID of the parent item.

Different taxonomies

If you have a custom taxonomy you want to display, you can pass the tax argument.

[menu-terms tax="product-categories"]

For example, maybe you have a product categories taxonomy for an e-commerce site, and you have subcategories under “Men’s”, “Women’s”, etc. If your “Men’s” term ID is 10 and your “Women’s” term ID is 20, you could display their subcategories in two different submenus using these two different instances of the shortcode:

[menu-terms tax="product-categories" child_of="10"]
[menu-terms tax="product-categories" child_of="20"]

The first would display only Men’s categories, and the second only Women’s

Exclude items

If you want to exclude specific items, you can do so by ID. List multiple IDs in a comma-separated string

[menu-terms exclude="15,20,76"]

The full set of get_terms parameters and what they do can be found in the WordPress Codex.

Full Code

Download the PHP & CSS

12 thoughts on “Displaying Categories Dynamically with UberMenu: Writing an auto-population shortcode

  1. Thanks for the brilliant tutorial Chris! surely will save a lot of time and stress now..one thing, you said to pick from specific category enter ID of parent item, where do we know what the IDs of our categories are?

    • Hi Zak,

      You’re welcome, glad you’ve found it useful!

      The way you can determine a category ID is to view your Categories in Posts > Categories, then hover over the edit link or click the link for a specific category to view the URL, and look for this in the URL tag_ID=92. In that example, 92 is the ID.

      Hope that helps!

      Chris

  2. here is what i have so far achieved with the above tutorial : http://blog.lovefashion.pk > TREND REPORT. the menu in GALLERY is all manual,time-consuming work..right now i have to figure out why the font color is different to that of the old menu, also how to figure out line spacing between categories

    • Hi Zak,

      If you want to change the hover color for this shortcode’s links, you can use

      #megaMenu ul.megaMenu .menu-terms a:hover{
        color:#f06793;
      }
      

      The Style Generator won’t affect these items because it doesn’t “know” about them (they’re not actually produced by UberMenu, but by this independent shortcode), so you have to write some custom styles for them.

      I’d also recommend that you use the “Full Width Submenu” option so that your links don’t have to wrap.

      If you have any further questions, let’s keep them over in the Support Forum – thanks!

      Chris

    • Hi Andreas,

      It looks like you just need to apply some CSS to change the colors and remove the item bullets. While we can’t offer customizations, if you have any questions about UberMenu you can post them over in the Support Forum

      Best,

      Chris

    • Hi Judith,

      You’re welcome, glad it helped you out! You could do something similar for child pages by using the get_posts function. This is actually something I’m working on building into UberMenu 3 🙂

      Best,

      Chris

  3. Hi Please review the menu Shop by Category
    This is not working for me.
    Can you please tell me why it is not working for me on this site ?

    • If you’re having trouble implementing the customization, I’d recommend waiting for UberMenu 3, where this functionality will be included out of the box. 🙂

      Best,

      Chris

  4. Hi,

    Nice tutorial, I tried to use this in of the website I develop, but I can’t make it correctly. I just followed all instruction except for the function.php because my child theme has no function.php included. So I put the code into the main theme. So I’m not sure if i’m doing it correctly. I’m using directory theme.

    The menu displayed already in the page without clicking the menu.

    Here’s the link of the site: Sample Menu link
    http://rockhamptonlife.com.au/

    Any suggestions is much appreciated.

    Thanks

    • Not really sure what you mean? Things seem to be working on your site for me. Note that this functionality is now built in to UberMenu 3. If you need any assistance, please post in the Support Forum – thanks! 🙂

Comments are closed.