How to add a section to the menus configuration, offering hard coded items?

So, let me explain what I’m actually trying to do, maybe there’s a better way to achieve it. I’m working on a theme, and I’d like the user to be able to easily position “special” items in the menu that will do something “special” in the themes frontend, in this case (amongst others) build nested menu structures based on custom taxonomies and their linked posts.

So I thought of a new “section” in the WordPress “Appearance > Menus” configuration, where some hard coded items would be offered, something like this:

Custom Menu Section

Now I could think of two ways how to handle that, a custom post type that is only shown in the nav menu, or a custom meta box (which seems to be a lot of work, at least wp_nav_menu_item_post_type_meta_box() looks like hours of trial and error).

I’m wondering whether any of these two options is the recommended way of solving this problem?

btw, I wouldn’t consider using wp_update_nav_menu_item() as an option since items created with it just seem to sit in the menu without an UI for re-adding them in case they are being removed. So this would require a custom meta box anyways, also it would save the items directly to the menu, breaking the “add item > save menu” convention people are used from the WordPress UI.

1 Answer
1

Menu items are a post type, and the menus to which they belong are taxonomy terms. So to create a post type in order to get a metabox from which you can add menu items seems a bit redundant (effectively duplicating every ‘special item’ in the database). In any case, setting the post type’s visibilty so that its only seen on the menus screen might be tricky.

Regardless this is quite an involved task, so I’ll just outline here how I would do this. You can see a ‘working example’ of this in this plug-in I built with Kaiser and Ryan Urban. That plug-in adds a post-type archive metabox for links to a post type’s archive page.

Adding the metabox

You’ll want to hook onto the admin_init hook and add_meta_box() (see codex). The screen ID for that page is nav-menus

add_action( 'admin_init', 'wpse102735_add_meta_boxe' );
public function wpse102735_add_meta_boxe() {
    add_meta_box(
        'wpse102735-metabox-id,
        'My Custom Section'
        'wpse102735_metabox_callback',
        'nav-menus',
        'side',
        'low'
    );
}

The function wpse102735_metabox_callback() is responsible for the content of the metabox. And should produce a checkbox-list of the ‘special items’ – all with a value/ID with which you can identify that item. (See metabox() method in the above mentioned plug-in, or this function in core which produces the HTML mark-up for the standard post/page metaboxes.

Javascript

In the previous step you should have added a ‘submit’ button. You’ll need to listen for when that button is clicked and then send a custom ajax request to ajaxurl (a global javascript variable pointing to WordPress’ ajax handler). The following is skeleton code:

jQuery( document ).ready( function($) {
     $( '#my-submit-id' ).click( function( event ) {
        event.preventDefault();

        var item_identifiers = []; 

        //Collect the IDs of the selected items and put them in $item_identifiers

        // Show spinner - you need to add this in the mark-up.
        $( '#wpse102735-metabox-id' ).find('.spinner').show();

        // Disable button
        $(this).prop( 'disabled', true );

        // Send checked post types with our action, and nonce
        $.post( ajaxurl, {
                action: wpse102735_add_menu_items,
                items: item_identifiers,
            },

            // AJAX returns html to add to the menu, hide spinner
            function( response ) {
                $( '#menu-to-edit' ).append( response );
                $( '#wpse102735-metabox-id').find('.spinner').hide();
                $(this).prop( 'disabled', false );
                            //Uncheck checkboxes too
            }
        );
    } );
} );

Note I’ve omitted nonces from the above (and below). You’ll need to add those in. Also you’ll need to properly register/enqueue the above script. See the referenced plug-in for details.

Handle the ajax

In the above we set the action to wpse102735_add_menu_items, we need that the ajax handler hook.

 add_action( 'wp_ajax_wpse102735_add_menu_items', 'wpse102735_ajax_handler' );

Our ajax handler will do two things:

  1. Create the menu items with wp_update_nav_menu_item() (best look at source / above plug-in)
  2. From the returned IDs, retrieve the menu objects and generate the HTML for the admin screen. (We appended the returned HTML to the menu in the previous step)

The ajax handler:

add_action( 'wp_ajax_wpse102735_add_menu_items', 'wpse102735_ajax_handler' );
function wpse102735_ajax_handler(){

   //Perform any necessary nonce/permission checks

   $items = $_POST['items']

   //For each item, set up the details for the menu item:
   foreach( $items as $item ){
      $menu_item_data= array(
         'menu-item-title'  => 'Item title',
         ...
      );

      // Collect the items' IDs.
      $item_ids[] = wp_update_nav_menu_item( 0, 0, $menu_item_data );
   }

   // If there was an error die here
   if( is_wp_error( $item_ids ) )
        die( '-1' );

   //Finally we want to generate mark-up to be added to the menu admin screen
   //First we set up the menu items we've just created

   // Set up menu items
   foreach ( (array) $item_ids as $menu_item_id ) {
        $menu_obj = get_post( $menu_item_id );
        if ( ! empty( $menu_obj->ID ) ) {
             $menu_obj = wp_setup_nav_menu_item( $menu_obj );
             // don't show "(pending)" in ajax-added items
             $menu_obj->label = $menu_obj->title;
             $menu_items[] = $menu_obj;
         }
    }

    //Now generate HTML using Walker_Nav_Menu_Edit walker

    // Needed to get the Walker up and running
    require_once ABSPATH.'wp-admin/includes/nav-menu.php';

    // This gets the HTML to returns it to the menu
    if ( ! empty( $menu_items ) ) {
         $args = array(
              'after'       => '',
              'before'      => '',
              'link_after'  => '',
              'link_before' => '',
              'walker'      => new Walker_Nav_Menu_Edit
         );

          echo walk_nav_menu_tree( $menu_items, 0, (object) $args );
     }

     exit();
}

I hope that gets you on the right track 🙂

Leave a Comment