In Part 3 of this series, we surveyed the various menu functions that the WordPress API provides. If you've been following along, then you know that we've already setup a settings page for our theme by using the add_theme_page
function. Although introducing menus and submenus aren't explicitly part of the Settings API, they play a role in building custom functionality, plugins, and/or themes.
In this article, we're going to introduce a new menu to the WordPress dashboard that will make our theme options available elsewhere other than just under the "Appearance" options.
Before we get started: This article assumes that you're familiar with the Settings API and theme options. If you're a beginner or even intermediate WordPress developer, I highly recommend catching up on the rest of the series before diving into this post.
A Look at the API
Because we've already looked at each of the menu functions, we don't need to rehash each of the functions that WordPress has available. Instead, we'll take a look at the ones we're going to use and then work through a practical example of how to use them in our own work.
Before we look at each function, let's detail what we're planning to accomplish in the next phase of this series:
- Introduce a top-level menu for our theme options
- Add a submenu item that will link to the "Display Options" tab
- Add a submenu item that will link to the "Social Options" tab
Relatively simple, right? In order to do this, we'll be taking advantage of the following two functions:
-
add_menu_page
which is used for introducing top-level menu items -
add_submenu_page
which is used to introduce sub-menu items to top-level menus.
We'll take a look at each function's parameters and usage as we implement them in our theme.
Note that the remainder of this article builds on this version of the WordPress Settings Sandbox. If you're following along with the repository, make sure you check it out.
The Top-Level Menu
The first thing that we want to do is introduce a top-level menu. This menu will appear directly below the "Settings" menu in the WordPress dashboard and will serve two purposes. The menu should:
- Expose the theme's options to the WordPress dashboard
- Display a default options page for the theme options
The function takes the following seven arguments, the first five are required, the last two are not:
-
page_title
is the text that will be rendered in the browser's title bar -
menu_title
is the text that will be displayed for the menu item -
capability
refers to the role that the user must have in order to access this menu -
menu_slug
is a unique value that identifies this menu. It's also how submenus register themselves with this menu. -
function_name
that is called when the menu is clicked for displaying the options page. -
icon_url
is the path to the icon that you want to display next to your menu item. -
position
is where the menu should be added in relation to the other menus in the WordPress Dashboard.
In our work, we'll be focused only on the first five parameters. I discuss menu positioning in the conclusion of this article.
To get started, we're going to need to introduce a call to the add_menu_page
function. According to the WordPress Codex, administration menus can be added using the admin_menu
hook. Earlier in this series, we wrote a function that adds our theme options to the "Appearance" menu. Specifically, we wrote sandbox_example_theme_menu
:
function sandbox_example_theme_menu() { add_theme_page( 'Sandbox Theme', // The title to be displayed in the browser window for this page. 'Sandbox Theme', // The text to be displayed for this menu item 'administrator', // Which type of users can see this menu item 'sandbox_theme_options', // The unique ID - that is, the slug - for this menu item 'sandbox_theme_display' // The name of the function to call when rendering this menu's page ); } // end sandbox_example_theme_menu add_action( 'admin_menu', 'sandbox_example_theme_menu' );
Note in the code above that this function was registered with the admin_menu
hook, as well. You should always strive to keep your functions logically consistent. Since we already have a function that registers a menu, that is registered with the appropriate hook, and since we're introducing similar functionality, we'll be adding our new menu functions to this function.
Add the following call to add_menu_page
directly under the call above:
add_menu_page( 'Sandbox Theme', // The value used to populate the browser's title bar when the menu page is active 'Sandbox Theme', // The text of the menu in the administrator's sidebar 'administrator', // What roles are able to access the menu 'sandbox_theme_menu', // The ID used to bind submenu items to this menu 'sandbox_theme_display' // The callback function used to render this menu );
As you can see, we're registering a menu that will display "Sandbox Theme" in both the browser's title bar and in the menu item. We're exposing the menu only to administrators and we've given the menu the unique ID of "sandbox_theme_parent_menu
". We'll be re-using this parameter in the next section.
There's one important thing that we need to clarify: Notice that we've passed 'sandbox_theme_display
' as the function to be called when this menu item is clicked. Recall that in Part 3 we introduced this function (and we refined it in Part 5). Specifically, it is responsible for rendering our tabbed theme options page.
By passing this existing function name to the add_menu_page
function, we're taking advantage of code that we've already written and we're rendering a default options page for the menu item.
At this point, we're ready to begin adding submenus but before moving forward, make sure that your function looks exactly like this:
function sandbox_example_theme_menu() { add_theme_page( 'Sandbox Theme', 'Sandbox Theme', 'administrator', 'sandbox_theme_options', 'sandbox_theme_display' ); add_menu_page( 'Sandbox Theme', 'Sandbox Theme', 'administrator', 'sandbox_theme_menu', 'sandbox_theme_display' ); } // end sandbox_example_theme_menu
Add the Submenus
Submenus are very similar to menus except that they "belong" to an existing menu. The API for registering submenus is relatively similar, too. The function accepts six arguments, the first five being required, the last one being optional:
-
parent_slug
refers to the unique ID of the parent menu item. In our case, "sandbox_theme_menu
". -
page_title
is the text to be rendered in the browser's toolbar when this submenu item is active -
menu_title
is the text for this actual submenu item in the dashboard -
capability
is the role that a user must have to access this menu item -
menu_slug
is the unique ID for this particular menu item -
function_name
that is called when the menu is clicked in order for displaying the options page
The function is straightforward. We have two menu items to introduce – one for "Display Options" and one for "Social Options."
Display Options
First, let's introduce a submenu item for display options. Add the following block of code directly under the add_menu_page
call that we defined above:
add_submenu_page( 'sandbox_theme_menu', // The ID of the top-level menu page to which this submenu item belongs 'Display Options', // The value used to populate the browser's title bar when the menu page is active 'Display Options', // The label of this submenu item displayed in the menu 'administrator', // What roles are able to access this submenu item 'sandbox_theme_display_options', // The ID used to represent this submenu item 'sandbox_theme_display' // The callback function used to render the options for this submenu item );
Each of the above parameters should be clear with the exception of the function name that we passed as the final argument. Notice that it's the same function name that we provided in the add_menu_page
call. But this makes sense, right? After all, the "Display Options" is the default tab that is displayed when the theme options are selected so it would make sense that it should be rendered when the top-level menu is selected and when the "Display Options" menu item is selected.
At this point, there's an important feature of WordPress to highlight: Note that once you've added your first submenu item, WordPress will actually render two submenu items to the top-level menu – one item that duplicates the function of the top-level menu and one item that corresponds to the submenu item that you just defined. I bring this up because, in my experience, I've seen developers get confused as to how (and why) this happens. The short of it is that WordPress is doing this – it's nothing wrong with your code.
Social Options
Adding a menu item for the social options is almost exactly like adding a menu item for the display options. Of course, we just want to change the values for the title bar, menu item, and the page that is displayed whenever the menu is selected. First, let's setup our call to the add_submenu_page
function. It should look like this:
add_submenu_page( 'sandbox_theme', 'Social Options', 'Social Options', 'administrator', 'sandbox_theme_social_options', 'sandbox_theme_display' );
Save your code, refresh your dashboard, and you should see the "Social Options" menu item now available under the "Sandbox Theme" menu; however, notice that clicking on the new submenu item only renders the "Display Options." Since we've passed the "sandbox_theme_display
" as the function name, that's what we should expect, right? So now we're faced with a little bit of a challenge: How do we select the "Social Options" tab when clicking on the submenu item?
Refactoring Our Tab Functionality
There are a couple of different options that we have for binding the new submenu item to the proper tab on the theme options' page:
- We can define a new function that renders out the social options. This would require that we do some additional work in introducing a new function, setting up tabbing functionality so that we don't break the experience of the existing page, and duplicating a little bit of code.
- We can refactor the existing
sandbox_theme_display
function to accept an optional parameter and then use an anonymous function in theadd_submenu_page
call to pass a parameter to it.
Ultimately, either of these options are up to you; however, I'd rather refactor my existing function than duplicate code so that's what I'll be doing throughout the remainder of this article.
First, let's begin refactoring our sandbox_theme_display
function. Let's have it accept an optional argument that will be used to indicate which tab we want to select. Locate the following signature in your functions.php file:
function sandbox_theme_display() { /* Consolidated for this part of the article. */ } // end sandbox_theme_display
Update the signature so that it accepts a single argument and sets it to null when it's not defined:
function sandbox_theme_display( $active_tab = null ) { /* Consolidated for this part of the article. */ } // end sandbox_theme_display
If you're new to PHP, you can read about default arguments on this page.
Remember from the last article that our display function is actually looking for a query string value to be set. We still want to maintain that functionality, but we need to account for the fact that the parameter may be passed into the function, as well. To perform this refactoring, first locate the following line of code in the sandbox_theme_display
function:
$active_tab = isset( $_GET[ 'tab' ] ) ? $_GET[ 'tab' ] : 'display_options';
Notice that this particular line of code is only concerned with the query string parameters. To account for the new optional parameter, we need to introduce logic that first checks if the query string parameter is checked, if not, it will check to see if the function's argument is set to display the social options, and, if not, then it will default to the display options. Replace the line of code above with the following conditional:
if( isset( $_GET[ 'tab' ] ) ) { $active_tab = $_GET[ 'tab' ]; } else if( $active_tab == 'social_options' ) { $active_tab = 'social_options'; } else { $active_tab = 'display_options'; } // end if/else
The final version of the function should look like this:
function sandbox_theme_display( $active_tab = '' ) { ?> <!-- Create a header in the default WordPress 'wrap' container --> <div class="wrap"> <div id="icon-themes" class="icon32"></div> <h2>Sandbox Theme Options</h2> <?php settings_errors(); ?> <?php if( isset( $_GET[ 'tab' ] ) ) { $active_tab = $_GET[ 'tab' ]; } else if( $active_tab == 'social_options' ) { $active_tab = 'social_options'; } else { $active_tab = 'display_options'; } // end if/else ?> <h2 class="nav-tab-wrapper"> <a href="?page=sandbox_theme_options&tab=display_options" class="nav-tab <?php echo $active_tab == 'display_options' ? 'nav-tab-active' : ''; ?>">Display Options</a> <a href="?page=sandbox_theme_options&tab=social_options" class="nav-tab <?php echo $active_tab == 'social_options' ? 'nav-tab-active' : ''; ?>">Social Options</a> </h2> <form method="post" action="options.php"> <?php if( $active_tab == 'display_options' ) { settings_fields( 'sandbox_theme_display_options' ); do_settings_sections( 'sandbox_theme_display_options' ); } else { settings_fields( 'sandbox_theme_social_options' ); do_settings_sections( 'sandbox_theme_social_options' ); } // end if/else submit_button(); ?> </form> </div><!-- /.wrap --> <?php } // end sandbox_theme_display
We're not quite done yet. Though we've done the necessary work to display the social options if the proper parameter has been passed, we haven't actually called the function using a parameter. To do this, we need to refactor the add_submenu_page
function from above. Currently, the function call looks like this:
add_submenu_page( 'sandbox_theme', 'Social Options', 'Social Options', 'administrator', 'sandbox_theme_social_options', 'sandbox_theme_display' );
We need to update the final parameter so that it calls the display function and passes the proper value for rendering the social options. To do that, we'll create an anonymous function:
add_submenu_page( 'sandbox_theme_menu', 'Social Options', 'Social Options', 'administrator', 'sandbox_theme_social_options', create_function( null, 'sandbox_theme_display( "social_options" );' ) );
If you're new to PHP, be sure to read up on the create_function
feature and anonymous functions. Though they are outside the scope of this article, they can be powerful (and useful!) when used in the proper context.
The final version of the sandbox_example_theme_menu
function should be as follows:
function sandbox_example_theme_menu() { add_theme_page( 'Sandbox Theme', // The title to be displayed in the browser window for this page. 'Sandbox Theme', // The text to be displayed for this menu item 'administrator', // Which type of users can see this menu item 'sandbox_theme_options', // The unique ID - that is, the slug - for this menu item 'sandbox_theme_display' // The name of the function to call when rendering this menu's page ); add_menu_page( 'Sandbox Theme', // The value used to populate the browser's title bar when the menu page is active 'Sandbox Theme', // The text of the menu in the administrator's sidebar 'administrator', // What roles are able to access the menu 'sandbox_theme_menu', // The ID used to bind submenu items to this menu 'sandbox_theme_display' // The callback function used to render this menu ); add_submenu_page( 'sandbox_theme_menu', // The ID of the top-level menu page to which this submenu item belongs 'Display Options', // The value used to populate the browser's title bar when the menu page is active 'Display Options', // The label of this submenu item displayed in the menu 'administrator', // What roles are able to access this submenu item 'sandbox_theme_display_options', // The ID used to represent this submenu item 'sandbox_theme_display' // The callback function used to render the options for this submenu item ); add_submenu_page( 'sandbox_theme_menu', 'Social Options', 'Social Options', 'administrator', 'sandbox_theme_social_options', create_function( null, 'sandbox_theme_display( "social_options" );' ) ); } // end sandbox_example_theme_menu add_action( 'admin_menu', 'sandbox_example_theme_menu' );
Conclusion
At this point, our theme now has its own top-level menu item with each of the settings tabs accessible via submenu items. Though this is useful, I believe it's important to note that there are some mixed opinions on introducing menus into the WordPress Dashboard. Although they can make your work more prominent and accessible, they may also interfere with existing WordPress menus or other third-party work especially if you attempt to place your menus somewhere using the position parameter. Though there is no absolute right or absolute wrong when it comes to introducing menus, think carefully about where you expose your menus. If an existing WordPress menu makes sense, register your work as a submenu.
In the next post, we'll begin taking a look at the various input elements that we can use to introduce options into our WordPress theme as well as how to validate and sanitize the data before serializing them.
Comments