Adding custom menu items to your menu

As you may have noticed I usually get inspired to write when I had some interesting project to do on my job. I found that those are the cases when I learn something new and I think that it’s good to share that with you (sharing is caring : ) ). Today’s example is something I have actually done many months ago, but it totally slipped my mind. Customizing menu items so that we can use pages and functionality from WooCommerce (in this particular example).

WordPress menu can be customized using the menu walker, but it can also be manipulated by adding your own menu items using wp_nav_menu_items filter hook. This filter allows us to add our own menu items to an already existing menu items.

Filters overview

WordPress filters are a great way to modify already existing data. WordPress has a lot of built in filter hooks that you can modify. Likewise you can create your own filter hook in the plugin you’re building, so that users can modify the data later on. For instance, let’s say you have a function that returns an array of values that you’d like to extend at some later point. In that case you’d want to create a function with your array, and check if the filter that will modify it exists, and then apply it to your array if it does:

function my_custom_array() {
$my_array = array(
'name' => 'Denis',
'age' => 28,
'title' => 'web developer',
);

if ( has_filter( 'add_additional_data' ) ) {
$my_array = apply_filters( 'add_additional_data', $my_array );
}

return $my_array;
}

Now since we’ve added a check for a filter that will add additional data, we can modify our array in an easy way

add_filter( 'add_additional_data', 'new_data' );

function new_data( $my_array ) {
$my_array['description'] = 'A totally cool guy';

return $my_array;
}

That way we can modify the data when we see fit. You can read more about it here. We are going to take advantage of the built in wp_nav_menu_items filter hook and add our own items.

Adding the new item

First example that I’ll show you is adding a ‘Log In’ menu item when you’re logged out, that will then turn to ‘My Account’ menu item drop down with WooCommerce endpoints when you’re logged in. You may be thinking: Why would you add the endpoints that way? They are already in the menu page. That’s completely true, but on the project I am currently working on, we thought that it would be a better user experience to have a login button, that will turn to my account menu item upon login. It is following the rationale of: if you don’t use it, don’t show it. And if you think about it — why would a person who isn’t logged in, and is not a member of whatever your site is (a custom shop, a digital agency, affiliate program, forum community, etc.), see the pages he or she isn’t going to use? Makes no sense to show it to them.

In my case just adding all the endpoints wasn’t going to cut it. Plus we added some additional custom menu items. So in your functions.php (or some file where you put your custom php additionals), add this code

// Check if the WooCommerce plugin is activated.
if ( in_array( 'woocommerce/woocommerce.php', get_option( 'active_plugins' ) ) ) {
add_filter( 'wp_nav_menu_items', 'add_loginout_link', 10, 2 );
}

/**
* Add login/my account link
*
* Adding additional link to header menu. You need to check the registered menu
* slug. In the case of Twenty Sixteen theme the registered menus are primary and
* social, we want the additional menu item to appear only on the primary (header) menu.
*
* @param string $items Additional menu items to append.
* @param object $args Menu arguments object.
* @return string Menu items modified.
* @since 1.0.0
*/
function add_loginout_link( $items, $args ) {
if ( 'primary' === $args->theme_location ) {
if ( is_user_logged_in() ) {
// WooCommerce endpoints.
$orders = wc_get_endpoint_url( 'orders', '', get_permalink( woocommerce_get_page_id( 'myaccount' ) ) );
$edit_account = wc_get_endpoint_url( 'edit-account', '', get_permalink( woocommerce_get_page_id( 'myaccount' ) ) );
$addresses = wc_get_endpoint_url( 'edit-adresses', '', get_permalink( woocommerce_get_page_id( 'myaccount' ) ) );
$payment_methods = wc_get_endpoint_url( 'payment-methods', '', get_permalink( woocommerce_get_page_id( 'myaccount' ) ) );
// Build menu item with subitems.
$items .= '<li class="menu_iem_logout"><a href="' . get_permalink( woocommerce_get_page_id( 'myaccount' ) ) . '">' . esc_html__( 'My Account', 'mytheme' ) . '</a>
<ul class="submenu">
<li><a href="' . esc_url( $orders ) . '">' . esc_html__( 'Orders', 'mytheme' ) . '</a></li>
<li><a href="' . esc_url( $edit_account ) . '">' . esc_html__( 'Account Details', 'mytheme' ) . '</a></li>
<li><a href="' . esc_url( $addresses ) . '">' . esc_html__( 'Addresses', 'mytheme' ) . '</a></li>
<li><a href="' . esc_url( $payment_methods ) . '">' . esc_html__( 'Payment Methods', 'mytheme' ) . '</a></li>
<li><a href="' . esc_url( wp_logout_url( home_url() ) ) . '">' . esc_html__( 'Logout', 'mytheme' ) . '</a></li>
</ul>
</li>';
} else {
$items .= '<li class="menu_iem_login"><a href="' . get_permalink( woocommerce_get_page_id( 'myaccount' ) ) . '">' . esc_html__( 'Log In', 'mytheme' ) . '</a></li>';
}
}
return $items;
}

First we check if we even have WooCommerce installed and activated. No point in adding a menu item that depends on WooCommerce if you cannot access the pages (my account endpoints). Next is the function that will actually add the menu item. We want to add our custom menu item only in the header menu, so we can check that the menu location is correct. In the case of Twentysixteen theme, the menu location for the header menu is called ‘primary’. In some cases this is ‘header-menu’ or something similar. If you are wondering what are the menu locations in your theme, just search for the register_nav_menus() function.

Next we want to cover two cases — when user is logged in, and when user is logged out. If user is logged out, show the ‘Log In’ menu item, and if her/she isn’t then show the ‘My account’ menu with dropdowns that you can customize. To get our desired endpoints, we’re using the wc_get_endpoint_url function that comes with WooCommerce — you need to specify the endpoint slug you want to get, and you must specify the permalink of My Account page, otherwise the link to your endpoint won’t work correctly (it will work correctly only if you’re on the my account page already).

After that it’s just building the html of your menu item, and you’re set.

Menu item logged in

Menu item when you’re logged in

Menu item logged out

Menu item when you’re logged out

Shopping cart in the menu

In the next example we’re going to add a cart drop down to our menu. First we need to add our menu item using the wp_nav_menu_items filter hook. You can add it right after our filter with add_loginout_link function

// Check if the WooCommerce plugin is activated.
if ( in_array( 'woocommerce/woocommerce.php', get_option( 'active_plugins' ) ) ) {
add_filter( 'wp_nav_menu_items', 'add_loginout_link', 10, 2 );
add_filter( 'wp_nav_menu_items', 'add_menu_cart', 10, 2 );
}

Then add the function

/**
* Add menu cart
*
* Adding shop cart to the navigation menu. You need to check the registered menu
* slug. In the case of Twenty Sixteen theme the registered menus are primary and
* social, we want the additional menu item to appear only on the primary (header) menu.
*
* @param string $items Additional menu items to append.
* @param object $args Menu arguments object.
* @return string Menu items modified.
* @since 1.0.0
*/
function add_menu_cart( $items, $args ) {
if ( 'primary' === $args->theme_location ) {
global $woocommerce;

wp_get_current_user();
$myaccount_page_url = get_permalink( woocommerce_get_page_id( 'myaccount' ) );

$items .= '<li id="shop_link_dropdown_cart" class="main-menu-item cart_right">
<a class="cart-contents" href="' . esc_url( $woocommerce->cart->get_cart_url() ) . '" title="' . esc_attr__( 'View your shopping cart', 'mytheme' ) . '"><i class="genericon genericon-cart"></i></a>
<div class="cart_dropdown_widget">' . get_the_widget( 'WC_Widget_Cart' ) . '</div>
</li>';

return $items;

}
}

The function is very similar to the previous one with the endpoints. But there is a catch — to actually output the widget, we’ll need to store it in the output buffer. That’s why we define a new function:

if ( ! function_exists( 'get_the_widget' ) ) {
/**
* Get the widget function
*
* Retrieves the widget and outputs it to the output buffer so that
* it can be shown.
*
* @param string $widget The widget's PHP class name.
* @param string|array $instance The widget's instance settings. Either an array or query-style string.
* @param string|array $args The widget's sidebar args. Either an array or query-style string.
* @return string HTML of the specified widget.
*/
function get_the_widget( $widget, $instance = '', $args = '' ) {
ob_start();
the_widget( $widget, $instance, $args );
return ob_get_clean();
}
}

This function will output the widget in the output buffer and return the contents of it. Adding some styling so that our dropdown looks pretty

/* Menu cart widget */

#shop_link_dropdown_cart{
position: relative;
}

#shop_link_dropdown_cart .cart-contents{
display: inline-block;
padding-top: 14px;
padding-left: 12px;
padding-bottom: 3px;
}

#shop_link_dropdown_cart .cart-contents .span_ci_icon-shopping-cart{
position: relative;
top: 0;
font-size: 24px;
color: #fff;
display: block;
}

#shop_link_dropdown_cart .cart_dropdown_widget{
position: absolute;
top: 42px;
right: 0;
background: #fff;
width: 350px;
border: 1px solid #d1d1d1;
padding: 0;
text-decoration: none;
list-style: none;
opacity: 0;
visibility: hidden;
-webkit-transition: all 200ms ease-in;
transition: all 200ms ease-in;
z-index: 10;
}

.woocommerce-cart #shop_link_dropdown_cart .cart_dropdown_widget{
display: none;
}


#shop_link_dropdown_cart:hover .cart_dropdown_widget{
opacity: 1;
visibility: visible;
}

#shop_link_dropdown_cart .cart_dropdown_widget:after{
content:"";
position: absolute;
top: -10px;
left: 0;
width: 100%;
height: 10px;
}

#shop_link_dropdown_cart .widget_shopping_cart{
margin: 0;
padding: 20px;
border: 0;
}

#shop_link_dropdown_cart .product_list_widget {
position: static;
border: 0;
}

#shop_link_dropdown_cart .product_list_widget:before,
#shop_link_dropdown_cart .product_list_widget:after {
display:none;
}

#shop_link_dropdown_cart .product_list_widget li{
border: 0;
padding-bottom: 25px;
}

#shop_link_dropdown_cart .product_list_widget li.empty{
padding-bottom: 0;
}

#shop_link_dropdown_cart .product_list_widget .remove{
background: none;
color: red!important;
}

#shop_link_dropdown_cart .product_list_widget li a:not(.remove){
padding-top: 12px;
padding-left: 25px;
width: auto;
}

#shop_link_dropdown_cart .product_list_widget li a:not(.remove) img {
width: 100px;
margin: 0;
}

#shop_link_dropdown_cart .product_list_widget li .quantity{
padding-left: 27px;
}

#shop_link_dropdown_cart .total{
border-top: 1px solid #d1d1d1;
}
Cart in the nav menu

We have our cart in the menu that will be shown when you hover on the little shop cart icon. Note that the icon font is included in the theme, if you have other icon font loaded, change the class of it accordingly.

Conclusion

That’s it, a simple way to add additional and custom ‘stuff’ to your menu items. In the same way you could add other widgets, or content in your menu. Notice that you are appending your new menu item to the end of the existing menu items, so you’ll be adding your link to the end of the menu you’ve created in the WordPress admin area.

I hope you find this tutorial useful. If you have any questions about this method of adding custom menu items to your theme, feel free to leave a comment below. Happy coding.


Originally published at Made by Denis.