WORDPRESS | Rest API & TWIG create menu ( without plugin )

This time i try to make a full wordpress website using REST API.
All go well until i had to work with navigation.
In my rest api json, there’s nothing about menus and navigation.
First thing that we can do is install a plugin , that create new andpoints:

WP API Menus

Basically, this plugin, create 4 new roots :

/menus list of every registered menu.
/menus/ data for a specific menu.
/menu-locations list of all registered theme locations.
/menu-locations/ data for menu in specified menu in theme location.

But, if i can, i prefer don’t use plugin, so, i tried to write my hown soluction.

We need two steps:

1° we need to create a new endpoint

2° We need to loop over the jsons that we obtain for prints the menu.

1) Create a new endpoint:

Open your theme function.php

function get_menu_restapi( WP_REST_Request $request ) {
  // Setup
      // prepare the returnable array
      $menu_items_to_return_as_json = array();
      $menu_items_to_return_as_json_item = array();
      //... get the slug
      $requested_menu_slug = $request->get_param('menu_slug');
      //... and then get the required menu items from the slug
      $menu_items_from_slug = wp_get_nav_menu_items('MainMenu');
  // Logic
      // If the menu exists and is not empty
      if (!empty($menu_items_from_slug)) :
        //... loop though each menu item
        foreach ($menu_items_from_slug as $menu_item) :
          //... and add the required information to the array that will parse back the JSON
          $menu_items_to_return_as_json[] = array(
            'id' => $menu_item->ID,
            'name' => $menu_item->post_name,
            'title' => $menu_item->title,
            'target' => $menu_item->target,
            'attr_title' => $menu_item->attr_title,
            'slug' => $menu_item->slug,
            'url' => $menu_item->url,
            'guid' => $menu_item->guid,
            'description' => $menu_item->description,
            'classes' => $menu_item->classes,
            'xfn' => $menu_item->xfn,
            'parent' => $menu_item->post_parent,
            'menu_order' => $menu_item->menu_order,
            'menu_item_parent' => $menu_item->menu_item_parent,
            'count' => $menu_item->count,
            'post_title' => $menu_item->post_title,
            'post_date' => $menu_item->post_date,
            'post_date_gmt' => $menu_item->post_date_gmt,
            'post_status' => $menu_item->post_status,
            'post_type' => $menu_item->post_type,
            'post_password' => $menu_item->post_password,
            'post_modified' => $menu_item->post_modified,
            'post_modified_gmt' => $menu_item->post_modified_gmt,
            'term_group' => $menu_item->term_group,
            'term_taxonomy_id' => $menu_item->term_taxonomy_id,
            'taxonomy' => $menu_item->taxonomy,
            'description' => $menu_item->description,
            'comment_status' => $menu_item->comment_status,
            'comment_count' => $menu_item->comment_count,
            'ping_status' => $menu_item->ping_status,
            'filter' => $menu_item->filter,
            'db_id' => $menu_item->db_id,
            'object_id' => $menu_item->object_id,
            'object' => $menu_item->object,
            'type' => $menu_item->type,
            'type_label' => $menu_item->type_label,
          );
        endforeach;
      endif;
  // Output
      // Return the required information
      return $menu_items_to_return_as_json;
}




// Run an anonymous function in to the REST API init hook
add_action( 'rest_api_init', function () {
  
  // Set up the required information
  $namespace = 'be_nav/';
  $endpoint = 'menus/(?P<menu_slug>\w+)';
  $args = array(
      // How the endpoint needs to be called
      'methods' => 'GET',
      // The callback function
      'callback' => 'get_menu_restapi',
  );
  // Add the route 'dcc/menus/<menu_slug>' to the WP REST API
  register_rest_route( $namespace, $endpoint, $args );
});

Those are the parameters that are inserted in the jsons of the navigation:

  • ‘id’ => $menu_item->ID – Single navigation voice ID
  • ‘name’ => $menu_item->post_name – Single navigation voice name
  • ‘title’ => $menu_item->title – Single navigation voice title
  • ‘target’ => $menu_item->target – Single navigation voice target
  • ‘attr_title’ => $menu_item->attr_title – Single navigation voice Title ( if is inserted )
  • ‘slug’ => $menu_item->slug – Single navigation voice Slug
  • ‘url’ => $menu_item->url – Single navigation voice Url
  • ‘guid’ => $menu_item->guid – Single navigation voice Globally Unique Identifier
  • ‘description’ => $menu_item->description – Single navigation voice Description ( if is inserted )
  • ‘classes’ => $menu_item->classes – Single navigation voice CLasses ( if is inserted )
  • ‘xfn’ => $menu_item->xfn – Single navigation voice target of the link
  • ‘parent’ => $menu_item->post_parent – The id of the parent page ( if is an ancestor )
  • ‘menu_order’ => $menu_item->menu_order – Single navigation voice menu order
  • ‘menu_item_parent’ => $menu_item->menu_item_parent – The id of the parent menu voice ( if is an ancestor )
  • ‘count’ => $menu_item->count
  • ‘post_title’ => $menu_item->post_title – The SEO Title of the post ( if is inserted )
  • ‘post_date’ => $menu_item->post_date – The POST Date
  • ‘post_date_gmt’ => $menu_item->post_date_gmt – The POST Date GMT
  • ‘post_status’ => $menu_item->post_status – The POST status
  • ‘post_type’ => $menu_item->post_type – The POST Type
  • ‘post_password’ => $menu_item->post_password – If post is a protected by password
  • ‘post_modified’ => $menu_item->post_modified – Date of the last post modification
  • ‘post_modified_gmt’ => $menu_item->post_modified_gmt – Date Gmt of the last post modification
  • ‘term_group’ => $menu_item->term_group
  • ‘term_taxonomy_id’ => $menu_item->term_taxonomy_id
  • ‘taxonomy’ => $menu_item->taxonomy,
  • ‘description’ => $menu_item->description – The SEO Description of the post ( if is inserted )
  • ‘comment_status’ => $menu_item->comment_status – The Comment status
  • ‘comment_count’ => $menu_item->comment_count – The Comment count
  • ‘ping_status’ => $menu_item->ping_status – The Ping status
  • ‘filter’ => $menu_item->filter – If menu has filters
  • ‘db_id’ => $menu_item->db_id – The ID inside the DB
  • ‘object_id’ => $menu_item->object_id – The DB ID of the original object this menu item represents
  • ‘object’ => $menu_item->object – The type of object originally represented, such as “category,” “post”, or “attachment.
  • ‘type’ => $menu_item->type The family of objects originally represented, such as “post_type” or “taxonomy.”
  • ‘type_label’ => $menu_item->type_label The singular label used to describe this type of menu item.

From now we can call our new Endpoint :

https://api.yourdomain.com/adminApi/wp-json/be_nav/menus/MainMenu

“be_nav” is the prefix that we had defined in the function.php in the “Add_action” function.

At this point we had our endpoint run, if we call it by browser, we can see the jsons of our “main menu” ( last parameter of endpoint is the slug of our menu, so if we had different menu, we can manage them by passing to the endpoint the different slug ).

Now we need to loop the jsons and create the html of our navigation.

Open the header.php and where you had your classical wordpress menu call, paste this ( it has the class of a foundation 6 navigation menu ):

<nav id="main-menu">
        <ul class="menu vertical medium-horizontal {{ main_nav.url }}" data-auto-height="true" data-responsive-menu="drilldown medium-dropdown" data-back-button='<li class="js-drilldown-back"><a tabindex="0">Your Back text</a></li>'>

    {% set count = 0 %} // Set main counter
    {% set submenu = 0 %} // Set Submenu Counter

    {% for key in main_nav|keys %} // Loop trough the menu elements exposed in the endpoint. 

       {% set link =  main_nav[key].url  %} // Retrive first level url
       {% set title =  main_nav[key].title   %} // Retrive first level title

       {% if main_nav[key].menu_item_parent == 0 %} // If the parameters "Menu Item Parent" is set to "0" it means that isn't a submenu. 
          {% set parent_id =  main_nav[key].id %} // Set "parent_id" equal to actual id
          <li class="item item_top" data-menu-item-parent="{{ main_nav[key].menu_item_parent }}" data-menu-submenu="{{ submenu }}" data-parentid="{{parent_id}}"> // Print the first level link 
            <a href="{{ main_nav[key].url  }}" class="title">
              {{ main_nav[key].title  }}
            </a>
       {% endif %}

       {% if parent_id == main_nav[key].menu_item_parent %} // If "parent_id" and "Menu item parent" are same, it means that we are on a sub menu of the main menu with id "parent_id"
       
          {% if submenu == 0 %}
            

            <ul class="sub-menu {{ submenu }}"> // Open the ul for the submenu

            {% set subcounter = 0 %} // set a counter of sub menu to 0
            {% for key in main_nav|keys %} // made a loop for know how many sub menu had the "parent_id" menu.
              {% if main_nav[key].menu_item_parent != 0 %} // Don't have submenu
                {% if main_nav[key].menu_item_parent == parent_id %}
                    {% set subcounter = subcounter +1 %} // increment subcounter of one
                  {% endif %}
              {% endif %}
            {% endfor %}

            {% if subcounter == 1 %}
              
            {% endif %}


          {% endif %}
          {% set submenu = submenu +1 %}// increment submenu of one
          <li class="item item_sub" data-subcontainer-lenght="{{subcounter}}" data-menu-item-parent="{{ main_nav[key].menu_item_parent }}" data-menu-submenu="{{ submenu }}" data-menu-subcounter="{{ subcounter }}" data-parentid="{{parent_id}}">
          <a href="<?php echo $link; ?>" class="title"> {{ main_nav[key].title  }} </a>
        </li> // Print submenu items
        {% if main_nav[key].menu_item_parent == parent_id and submenu > subcounter-1 %} // If we are at the last submenu  ( subcounter is equal to the length of the submenu )
        
        {% set submenu = 0 %} 
          </ul> // Close submenu ul
         

       {% endif %}
       {% if menu_item_parent != parent_id %}
       </li> // Close main menu that contain submenu
       {% endif %}
       {% endif %}
    {% endfor %}

</ul>

</nav>

Basically we made a loop trough jsons and if ” menu_item_parent ” is set to 0 , it means that is a first level menu, if not, we loop trough jsons for know how many submenu had this voice, and so , we print all sub menu.

That’s all