2

I am creating a custom admin section. I have the following code:

// Top level menu
add_menu_page('Books', 'Books', 'publish_posts', 'books', 'render_books_page', '', 17);

// Adding this function to make the first submenu have a different name than the main menu
// Details: https://wordpress.stackexchange.com/questions/66498/add-menu-page-with-different-name-for-first-submenu-item
add_submenu_page('Books', 'Books', 'All Books', 'publish_posts', 'books', 'render_books_page');

// The Add Book menu page
add_submenu_page('Books', 'Add New Book', 'Add Book', 'publish_posts', 'add-book', 'render_add_book_page');

// The Edit Book menu page (this page is hidden from the menu, and accessed via the All Books page only)
add_submenu_page(null, 'Edit Book', 'Edit Book', 'publish_posts', 'edit-book', 'render_edit_book_page');

As you notice in the last line of code, the first parameter of the add_submenu_page() is set to null. This is to ensure that the Edit Book page is hidden (more details about this here). Access to the Edit Book page is done via the main menu, from the list of all books.

The problem is, when I go to the Edit Book page, the Admin Menu to the left collapses (on the other hand, the default WordPress behaviour is as follows: If you go to an Edit Post page, or the Edit Page page, both the Posts and Pages menu stay expanded for their respective edit pages). In my case, the menu collapses.

How can I keep the menu to the left expanded when I go to the Edit Book page, to behave in a similar fashion to that of WordPress?

Thanks.

6
  • Just curious, why not use a custom post type instead of creating a bunch of menus? That'd for sure mimic normal WordPress behavior.
    – Ian
    Mar 17, 2017 at 5:22
  • We investigated this, however we have to use menus, because we have customized actions associated with saving the list of books (for example: we have to save on a different server using REST APIs; we also have to see if the user purchased our premium keys to be able to save for certain accounts). There are many other reasons that we thought it is better to start from scratch rather than customize the custom post types.
    – Greeso
    Mar 17, 2017 at 5:26
  • Oh, if you need to save data to a different server you could always utilize the save_post hook and perform your REST API code when saving via a custom post type (or any post type). codex.wordpress.org/Plugin_API/Action_Reference/save_post
    – Ian
    Mar 17, 2017 at 5:29
  • Also loading the list of books on the main menu is from that server, again via REST API. How would this work? And the content of the ADD and EDIT pages are completely different from a regular page (We are rendering some serious stuff there). Could those be also customized? Deleting an item also is done via REST (but moves it to TRASH, and hence we have to also customize the delete action)
    – Greeso
    Mar 17, 2017 at 5:31
  • Lets say you're saving books via custom post type and hooking the save action to your REST functions with the save_post hook...would the list of books you've saved in WordPress be different from the books on the other server? If they are the same, you could simply query the book custom type and in the while look execute the add_submenu_page function for each book. Or is the list of books on that secondary server not the same as what would potentially be posted in WP?
    – Ian
    Mar 17, 2017 at 5:36

3 Answers 3

3

For your particular situation, where you need to have a menu registered, but not shown unless you click on it from a link on another page you can add a conditional check to see if you're on the editing page. If so, then change replace null with book per the add_submenu_page() parameters.

add_action( 'admin_menu', 'add_the_menus' );
function add_the_menus() {

    // Top level menu
    add_menu_page( 'Books', 'Books', 'publish_posts', 'books', 'render_books_page', '', 17 );

    // Adding this function to make the first submenu have a different name than the main menu
    add_submenu_page( 'books', 'Books', 'All Books', 'publish_posts', 'books', 'render_books_page' );

    // The Add Book menu page
    add_submenu_page( 'books', 'Add New Book', 'Add Book', 'publish_posts', 'add-book', 'render_add_book_page' );

    // Check the get parameter for page to see if its the page you want to display in the menu only when you're on it.
    if ( $_GET['page'] === 'edit-book' ) {
        // The Edit Book menu page (this page is hidden from the menu, and accessed via the All Books page only)
        add_submenu_page( 'books', 'Edit Book', 'Edit Book', 'publish_posts', 'edit-book', 'render_edit_book_page' );
    } else {
        // The Edit Book menu page (this page is hidden from the menu, and accessed via the All Books page only)
        add_submenu_page( null, 'Edit Book', 'Edit Book', 'publish_posts', 'edit-book', 'render_edit_book_page' );
    }
}

An additional note. If it turns out that you need to also keep that menu item hidden even when selected you can enqueue a style to hide it only when you're on that page.

add_action( 'admin_enqueue_scripts', function () {
    if ( $_GET['page'] === 'edit-book' ) {
        wp_enqueue_style( 'book-edit', get_stylesheet_directory_uri() . '/assets/css/book-edit.css' );
    }
} );

Where the contents of book-edit.css would be something as simple as:

#toplevel_page_books li.current {
    display: none;
}
6
  • Good way of thinking, completely out of the box. However, unfortunately does not work. This solution adds the Edit Book menu item (also makes it highlighted in white) when I am at the Edit Book page. This is not the right behaviour. When I go to the Edit Book page, the Edit Book menu should still be hidden, and the All Books menu should be highlighted.
    – Greeso
    Mar 17, 2017 at 6:12
  • I think you gave me an idea of what to do. Hold on, let me update and get back to you
    – Greeso
    Mar 17, 2017 at 6:16
  • I just edited my answer with a way to hide the edit-book page with css.
    – Ian
    Mar 17, 2017 at 6:24
  • I am getting somewhere. I will keep you posted.
    – Greeso
    Mar 17, 2017 at 6:37
  • OK, what you suggested is not exactly the correct solution. But you triggered some ideas that helped me find the solution (based on your suggestion). So, for the benefit of the crowd, is it OK that I update your solution to reflect the correct answer? (if yes, I will update tomorrow, as I am super tired now)
    – Greeso
    Mar 17, 2017 at 6:53
1

The solution is based on the ideas provided by @Ian. Thanks.

add_action( 'admin_menu', 'add_the_menus' );
function add_the_menus() {

    // Top level menu
    add_menu_page ('Books', 'Books', 'publish_posts', 'books', 'render_books_page', '', 17);

    // Adding this function to make the first submenu have a different name than the main menu
    add_submenu_page('books', 'Books', 'All Books', 'publish_posts', 'books', 'render_books_page' );

    if ((isset($_GET['page'])) && ($_GET['page'] === 'edit-book')) {
        // The Edit Book menu page and display it as the All Books page
        add_submenu_page('books', 'Edit Book', 'All Books', 'publish_posts', 'edit-book', 'render_edit_book_page' );
    }

    // The add-book menu page
    add_submenu_page('books', 'Add New Book', 'Add New', 'publish_posts', 'add-book', 'render_add_book_page' );
}

And we must hide the first menu item

add_action( 'admin_enqueue_scripts', function () {

    if ((isset($_GET['page'])) && ($_GET['page'] === 'edit-book')) {

        // Load CSS file
        wp_enqueue_style('book-edit', 'path/to/css/menu.css');

        // Load jQuery
        wp_enqueue_script('jquery');

        // Load 
        wp_enqueue_script('book-edit-script', 'path/to/js/menu.js');
    }
});

And the content of menu.css is:

#toplevel_page_books li.current {
    display: none;
}

#toplevel_page_books li.wp-first-item {
    display: list-item;
}

Also the content of 'menu.js' is:

jQuery(document).ready(function($) {

    $('#toplevel_page_books li.wp-first-item').addClass('current');

});

TL;DR

To understand how all this works, here is a step-by-step explanation.

Step 1: We add the main menu item (the books menu item) to display the list of books

add_action( 'admin_menu', 'add_the_menus' );
function add_the_menus() {

    // Top level menu
    add_menu_page ('Books', 'Books', 'publish_posts', 'books', 'render_books_page', '', 17);
}

Step 2: We add the add-book menu item as a submenu to the main books menu item

add_action( 'admin_menu', 'add_the_menus' );
function add_the_menus() {

    // Top level menu
    add_menu_page ('Books', 'Books', 'publish_posts', 'books', 'render_books_page', '', 17);

    // The add-book menu page
    add_submenu_page('books', 'Add New Book', 'Add New', 'publish_posts', 'add-book', 'render_add_book_page' );
}

Step 3: Finishing Step 2 above adds the books menu item, The menu list on the left side would look like this:

Books              <---------- This is the main top level menu names
  Books            <---------- This is the first sub-menu
  Add New          <---------- This is the second sub-menu

However, we should fix this. The intended list should look like this

Books              <---------- This is the main top level menu names
  All Books        <---------- This is the first sub-menu
  Add New          <---------- This is the second sub-menu

To do this, we have to modify our code as follows:

add_action( 'admin_menu', 'add_the_menus' );
function add_the_menus() {

    // Top level menu
    add_menu_page ('Books', 'Books', 'publish_posts', 'books', 'render_books_page', '', 17);

    // Adding this function to make the first submenu have a different name than the main menu
    add_submenu_page('books', 'Books', 'All Books', 'publish_posts', 'books', 'render_books_page' );

    // The add-book menu page
    add_submenu_page('books', 'Add New Book', 'Add New', 'publish_posts', 'add-book', 'render_add_book_page' );
}

Step 4: Next we should add a sub menu to edit books (the edit-book menu item). After adding his submenu, and when we are at the edit-book page, the menu on the left should look like this:

Books
  All Books        <---------- When we are in the 'edit-book' page, this menu item is selected and is highlighted (typically white in color), and also clicking on "All Books" would return us back to the "All Books" page.
  Add New

The solution I tried first was what I posted in my original question, which did not work exactly. So, based on discussions with @Ian and looking at his proposed solution, I came up with this:

add_action( 'admin_menu', 'add_the_menus' );
function add_the_menus() {

    // Top level menu
    add_menu_page ('Books', 'Books', 'publish_posts', 'books', 'render_books_page', '', 17);

    // Adding this function to make the first submenu have a different name than the main menu
    add_submenu_page('books', 'Books', 'All Books', 'publish_posts', 'books', 'render_books_page' );

    // If we are in the 'edit-book' page, then display the 'edit-book' submenu, otherwise, display the regular 'books' menu
    if ((isset($_GET['page'])) && ($_GET['page'] === 'edit-book')) {
        // Display the 'edit-book' menu page and display it as the 'all-books' page
        // Notice that the slug is 'edit-book', but the display name is 'All Books'
        add_submenu_page('books', 'Edit Book', 'All Books', 'publish_posts', 'edit-book', 'render_edit_book_page' );
    }

    // The add-book menu page
    add_submenu_page('books', 'Add New Book', 'Add New', 'publish_posts', 'add-book', 'render_add_book_page' );
}

Now, if we click on the 'books' menu item, or the 'add-book' menu item, then everything is fine. However, if we try to edit an existing book then the following menu list will be displayed

Books
  All Books        <---------- This is the first sub-menu (due to the first submenu call)
  All Books        <---------- This is the 'edit-book' page (HIGHLIGHTED)
  Add New

Step 5: Now we notice the following: By clicking on the first submenu, the "All Books" page will be rendered, and clicking on the second submenu will render the "Edit" page; and in our case, we want to render the "All Books" page. Therefore, we have to hide the SECOND submenu and make the first submenu highlighted. This is done as follows:

add_action( 'admin_enqueue_scripts', function () {

    if ((isset($_GET['page'])) && ($_GET['page'] === 'edit-book')) {

        // Load CSS file
        wp_enqueue_style('book-edit', 'path/to/css/menu.css');

        // Load jQuery
        wp_enqueue_script('jquery');

        // Load 
        wp_enqueue_script('book-edit-script', 'path/to/js/menu.js');
    }
});

And the content of menu.css is:

#toplevel_page_books li.current {
    display: none;
}

#toplevel_page_books li.wp-first-item {
    display: list-item;
}

Also the content of 'menu.js' is:

jQuery(document).ready(function($) {

    $('#toplevel_page_books li.wp-first-item').addClass('current');

});

And now everything works like a charm.

5
  • Nice solution! Glad I could help spur some ideas :)
    – Ian
    Mar 17, 2017 at 19:04
  • All thanks to you. I hate to do this, but I will accept my answer. I was hoping I change your answer so I can accept it and you get your dues from it, but you stated I should do this.
    – Greeso
    Mar 17, 2017 at 20:37
  • No worries. The whole point isn't to get credit, but to help! Your answer fit your exact needs so it should be the answer ;)
    – Ian
    Mar 18, 2017 at 2:18
  • Just advice, For printing individual page styles you can use code like this : $this->page_hook = add_submenu_page(...); add_action('admin_print_styles-' . $this->page_hook, function); add_action('admin_print_scripts-' . $this->page_hook, function); Aug 28, 2018 at 14:13
  • For highlighting u can update submenu_file with hook and use only css for hiding menu item. ` add_action( 'admin_head', function () { global $parent_file, $submenu_file, $post_type; $screen = get_current_screen(); if ( $screen->id === 'your-page-id' ) { $submenu_file = 'page_slug_highlight'; } } ); ` Aug 28, 2018 at 14:22
0

Actually, you just have to register your menu normally, with the declaration of the parent page like so:

function create_menu_and_submenu_page(){

    // Top level menu
    add_menu_page(
        'Books',
        'Books',
        'publish_posts',
        'books',
        'render_books_page',
        '',
        17
    );

    // Here the other subpages
    // ...

    // Submenu page
    add_submenu_page(
        'books',        // Here put the parent slug
        'Edit Book',
        'Edit Book',
        'publish_posts',
        'edit-book',
        'render_edit_book_page'
    );
}

And then, you simply have to hide it like that:

// We remove the unecessary links from the left menu
// while keeping the plugin menu selected
add_action( 'admin_head', function() {
    remove_submenu_page( 'books', 'edit-book' );  // 'parent-slug', 'subpage-slug'
} );

Hope it will help someone else in the future. Happy Coding :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.