How to create an “enrolled courses” submenu in LearnDash
Someone in the LearnDash Facebook group asked how to create a submenu populated with enrolled courses. So I built it for fun:
This is one of those things that is difficult to write a “LearnDash tutorial” about, because this is just as much of a WordPress theme customization question than it is a LearnDash question.
How would you inject any sort of content into the menu of your theme?
What if you wanted a submenu of “Products Purchased” from WooCommerce?
What if you just wanted to inject a series of static links into a submenu, and didn’t feel like using the built-in interface?
So while it would be ideal if we handled all the menu building and rendering on the server side, I thought of a way to leverage JavaScript that should make it far easier to implement on any theme.
If you’re cool with that, read on.
Step #1: Get a theme that supports submenus
While all of my Themetry themes support submenus, themes can do pretty much whatever they want, including not support menus at all.
We’re going to assume that you have a theme that already supports submenus. If yours doesn’t, consider switching themes, or hire a developer to help build it for.
Pretty much any well-made and modern theme will support submenus, so if your publicly-released theme doesn’t then I would heavily lean toward switching because that seems like a gigantor red flag to me.
Step #2: Create a menu item as you normally do
Make the menu item structure like this:
- Label: Courses. URL: Whatever. Could be # or your course archive page.
- Label: ERROR ERROR. URL: #enrolled-courses
Or see the following screenshot for an illustration.
The labels don’t matter much. I made the second label “ERROR ERROR” because no user should actually see that.
What really matters is the URL of the submenu item equals exactly the following: #enrolled-courses
That is because we will be using JavaScript to specifically target that item to find our where we should be injecting enrolled courses.
Step #3: Add the snippet
Some customization may be required. See the “POTENTIAL CUSTOMIZATION” comments in the JavaScript for more information.
add_action( 'wp_footer', 'lmscoder_enrolled_courses_dropdown' ); function lmscoder_enrolled_courses_dropdown() { // Go away if not logged in if ( ! is_user_logged_in() ) { return; } // Grab enrolled courses $enrolled_course_ids = learndash_user_get_enrolled_courses( get_current_user_id() ); // Go away if no enrolled courses if ( ! $enrolled_course_ids ) { return; } // Build menu list array $menu_list = []; foreach ( $enrolled_course_ids as $id ) { $menu_list[] = [ 'url' => get_permalink( $id ), 'title' => get_the_title( $id ), ]; } // JSONify it for next step $menu_list_json = json_encode( $menu_list ); ?> <script> // Convert to JavaScript object const menu_obj = JSON.parse( '<?php echo $menu_list_json; ?>' ); // POTENTIAL CUSTOMIZATION: ensure li and a classes match theme const menu_html = menu_obj.map( item => ` <li class="menu-item"> <a class="menu-link" href="${ item.url }">${ item.title }</a> </li> ` ).join( '' ); // This should not change if you follow tut const beacon = document.querySelector( 'a[href="#enrolled-courses"]' ); // POTENTIAL CUSTOMIZATION: Select the closest submenu element. const submenu = beacon.closest( '.sub-menu' ); // Destroy the beacon beacon.remove(); // Add enrolled courses to submenu submenu.insertAdjacentHTML( 'beforeend', menu_html ); </script> <?php }
I happened to use the Astra theme when writing this snippet, so if you’re using that, you should be set.
If you’re not, and the snippet does not work as-is, there’s really no way around this other than to inspect your theme and see what CSS classes are used to style submenu items.
What if the user is logged out, or not enrolled in courses?
Okay, well the function will stop if that’s the case. But the “ERROR ERROR” menu item will remain. Not very professional!
Here’s a way to just get rid of the whole menu item:
add_action( 'wp_footer', 'lmscoder_enrolled_courses_menu_destroyer' ); function lmscoder_enrolled_courses_menu_destroyer() { // Go away if logged in if ( is_user_logged_in() ) { return; } // Grab enrolled courses $enrolled_course_ids = learndash_user_get_enrolled_courses( get_current_user_id() ); // Go away if there are enrolled courses if ( $enrolled_course_ids ) { return; } ?> <script> function lmscoderEnrolledCoursesMenuDestroyer() { // POTENTIAL CUSTOMIZATION: Adjust with actual menu item ID const unwanted_menu = document.querySelector( '#menu-item-28673' ); if ( ! unwanted_menu ) { return; } unwanted_menu.remove(); } lmscoderEnrolledCoursesMenuDestroyer(); </script> <?php }
Assuming your menu item ID is not also 28673, that will need to be customized. Once again, no way around this other than inspecting your site.
With this snippet, we could traverse up from that #enrolled-courses link again. Although since you may need to inspect to find the appropriate “parent menu item” selector anyway, I figured it would be easier to just select the menu item ID directly.
Something like Conditional Menus might help to at least check if a user is logged in or not, although it may not account for a logged in user that is not enrolled in any courses.
We want to make sure that “ERROR ERROR” menu item never appears. 🙂