Skip to content
M. Reza Lavaryan edited this page Nov 13, 2016 · 7 revisions

Installation

To install it, use composer:

composer require lavary/menus

Creating Menus

You can create a menu by using make() method. Here's a basic usage:

<?php

use Lavary\Menus\MenuBuilder;

$builder = new MenuBuilder();

$builder->make('MyNavBar', function($menu) {
  $menu->add('Home');
  $menu->add('About', 'about');
  $menu->add('services', 'services');
  $menu->add('Contact', 'contact');
});

make() accepts a name and a callable as the argument. This is where you can define your menu items - by using add() method.

make() creates a root node, which is an instance of \Lavary\Menus\Item itself (This root node does nothing but acts as the parent container for other items ). Then, it calls the passed in callable with the root node object as the argument . Finally, within the callback function, the items are added to the root node.

Adding Items

To define the menu items, we use add() method. add() accepts two arguments, the first parameter is the item's title, and the second one is either a simple URL or an array of options. The option's array may contain a number of HTML attributes or a group of settings to define the item's characteristics.

<?php

use Lavary\Menus\MenuBuilder;

$builder = new MenuBuilder();

$builder->make('MyNavBar', function($menu) {
  // Simple URLs
  $menu->add('Home');
  $menu->add('About', 'about');
  
  // Using the option array
  $menu->add('services', ['url' => 'services', 'class' => 'foo']);
  $menu->add('Contact', ['url' => 'contact', 'data-role' => 'item']);
});

Each menu that you create with make() is stored in $collection attribute of MenuBuilder class . This means we can manage several menus (for example, a sidebar or a navigation menu with just one instance of MenuBuilder class).

<?php

use Lavary\Menus\MenuBuilder;

$builder = new MenuBuilder();

// First Menu
$builder->make('MyNavBar', function($menu) {
  $menu->add('Home');
  $menu->add('About', 'about');
  $menu->add('services', 'services');
  $menu->add('Contact', 'contact');
});

// Second menu
$builder->make('SideBar', function($menu) {
  $menu->add('Item', 'item/url');
})

In the preceding code, we have two root nodes MyNavBar and SideBar which are stored in property $collection of $builder class.

Getting Menus from the Collection

To fetch a certain menu from the collection, you can use get() :

<?php
// ...
$navbar  = $builder->get('MyNavBar')->asUl();
$sidebar = $builder->get('SideBar')->asBootstrap();
// ...    

Rendering the Menus

Menus currently provides four rendering method out of the box: asUl(), asOl(), asDiv(), and of course asBootstrap().

// ...
echo $menu->get('MyNavBar')->asUl();

Which outputs:

<ul>
  <li><a href="">Home</a></li>
  <li><a href="/about">About</a></li>
  <li><a href="/services">Services</a></li>
  <li><a href="/contact">Contact</a></li>
</ul>

Here's another example:

// ...
echo $menu->get('MyNavBar')->asDiv();

Which outputs:

<div>
  <div><a href="">Home</a></div>
  <div><a href="/about">About</a></div>
  <div><a href="/services">Services</a></div>
  <div><a href="/contact">Contact</a></div>
</div>

You can also pass an array of attributes to the rendering methods. They will be rendered as HTML attributes for the main container:

// ...
echo $menu->get('myNavBar')->asUl(['data-role' => 'menu', 'class' => 'nav nav-bar navbar-inverse']);
// ...

will output:

<ul data-role="menu" class="nav navbar navbar-inverse">
  <li><a href="">Home</a></li>
  <li><a href="/about">About</a></li>
  <li><a href="/services">Services</a></li>
  <li><a href="/contact">Contact</a></li>
</ul>

Note When you're calling asBootstrap(), just make sure you have Bootstrap's Javascript and CSS files included in your template.

Custom Renderers

You can also create your custom renderers by extending the abstract class Lavary\Menus\Renderer\Element. Then, call render() method, passing an instance of your custom renderer to it.

<?php

// ...
$customRenderer = new CustomRenderer();
$builder->get('myNavBar')->render($customerRenderer);
// ...

The abstract class forces you to implement a method named render().

Rendering a Portion of the Menu

You can also render a portion of the menu instead of the whole menu:

<?php
// ...
$builder->get('myNavbar')->get('about')->asBootstrap();

The preceding example will only render the children of item About.

Or you can use the magic "Where" method to query through the items and render the result:

<?php

use Lavary\Menus\MenuBuilder;

$builder = new MenuBuilder();

// First Menu
$builder->make('MyNavBar', function($menu) {
  $menu->add('Home')->data('show' => true);
  $menu->add('About', 'about')->data('show' => true);
  $menu->add('services', 'services')->data('show' => true);
  $menu->add('Contact', 'contact')->data('show' => true);
});

$builder->get('MyNavBar')->whereShow(true);

Magic "Where" methods are explained in detail in the next sections.

URLs

You define the item's URL by passing the URL as the second argument to the add() method:

<?php
// ...
$menu->add('About Us', 'about-us');
// ...

Or by specifying the url option in the options array:

<?php
// ...
$menu->add('About Us', ['url' => 'about-us']);
// ...

Sub-Items

Needless to say, items can have sub-items:

<?php

use Lavary\Menus\MenuBuilder;

$builder = new MenuBuilder();

$builder->make('MyNavBar', function($menu) {
  
  $menu->add('Home');
    
  $menu->add('About', 'about');   
    $menu->about->add('History', 'about/history');
     $menu->about->add('Goals', 'about/goals');
       $menu->about->goals->add('Another Item', 'about/goals/another/item')
         $menu->about->goals->anotherItem('And another item', 'another/item')
  
  $services = $menu->add('services', 'services');
    $services->add('Development', 'about/development');
    $services->add('Consultation', 'about/consultation');

  $menu->add('Contact', 'contact');
});

As you can see, we can sequentially chain the items to go as deep as required.

This is done by referring to the item's nickname, which is the item's title in camel-case format:

<?php
// ...
$menu->add('What We Do');

// ... and this is how we refer to it:
$menu->whatWeDo->add('Sub item', 'sub/item');
// ...

Since add() returns the item's object, we can store it in a variable for further reference:

<?php
// ...
$whatWeDo = $menu->add('What We Do');

// ... and this is how we refer to it:
$whatWeDo->add('Sub item', 'sub/item');
// ...

You can also use get() method to get the item by nickname:

<?php
// ...
$menu->add('What We Do');

// ... and this is how we refer to it:
$menu->get('whatWeDo')->add('Sub item', 'sub/item');
// ...

All these three ways are identical.

Sub-items are stored within $children property of the parent item.

$children is an instance of Lavary\Menus\Collection. You can get it by calling getChildren() against the parent item.

Setting Item's ID Manually

When you add a new item, a unique ID (which is a random string) is automatically assigned to the item. However, there are times when you need to define them manually. As an example when you're fetching the menu data from a database.

To set the ID manually, you can use setId() method, passing your desired ID:

<?php
// ...
$menu->add('About', array('url' => 'about'))
     ->setId('74398247329487')
// ...

Alternatively, you can pass the ID as an element of the options array - when creating the item:

<?php
// ...
$menu->add('About', array('url' => 'about', 'id' => 74398247329487));
// ...

You can get the Item's ID by calling getId() against the item object.

Setting Item's Nickname Manually

When you add a new item, a nickname - which is a camel-case form of the item's title - is automatically assigned to the item for reference. For example, an item with the title About Us would have the nickname aboutUs.

Again there are times when you have to explicitly define the item's nickname. To do this, you can use setNickname()

<?php
// ...
$menu->add('About', array('url' => 'about'))
     ->setNickname('about_menu_nickname');
     
// And use it like you normally would
$menu->get('about_menu_nickname');     
     
// ...

You can also pass the nickname as an element of the options array:

<?php
// ...
$menu->add('About', array('url' => 'about', 'nickname' => 'about_menu_nickname'));

// And use it like you normally would
$menu->item('about_menu_nickname');    
// ...

To get the item's nickname, just call getNickname() as below:

<?php
// ...
$menu->add('About', array('url' => 'about', 'nickname' => 'about_menu_nickname'));

var_dump($menu->about->getNickname());
// ...

Referring to Items

You can access defined items throughout the code from inside and outside of the callback function.

Getting Items by Nickname

As mentioned in the basic examples earlier, we can use item's nickname to refer to it:

<?php

use Lavary\Menus\MenuBuilder;

// ...
$builder = new MenuBuilder();

$builder->make('MyNavBar', function($menu) {
  $menu->add('Home');
  // ...

  // To refer to the item
  $home = $menu->home;
  // or
  $home = $menu->get('home');
});

Or the get the items from outside the callable:

<?php

use Lavary\Menus\MenuBuilder;

// ...
$builder = new MenuBuilder();

$builder->make('MyNavBar', function($menu) {
  $menu->add('Home');
  // ... 
});

$home = $builder->get('myNavBar)->home;
// Or
$home = $builder->get('myNavBar)->get('home');

If you're not comfortable with the above methods, you may want to store the item in a variable:

<?php
// ...
$about = $menu->add('About', 'about');

$about->add('Who We Are', 'who-we-are');
$about->add('What We Do', 'what-we-do');
// ...

Getting Items by Id

You can also get an item by ID, by using find() method:

<?php
// ...
$item = $menu->find(74398247329487)
// ...

Getting All Items

To get all the items, you can call getChildren() against the root node or any item with children - in that case, the children of that item are returned.

Here's an example:

<?php

use Lavary\Menus\MenuBuilder;

$builder = new MenuBuilder();

$builder->make('MyNavBar', function($menu) {
  $menu->add('Home');
  $menu->add('About', about');
  $menu->add('services', 'services');
  $menu->add('Contact', 'contact');
});

$items = $builder->get('MyNavBar')->getChildren();

// ...

getChildren() returns an instance of \Lavary\Menus\Collection. Since this collection implements the Arrayable interface, you can use it with all the PHP's array functions.

<?php

use Lavary\Menus\MenuBuilder;

$builder = new MenuBuilder();

$builder->make('MyNavBar', function($menu) {
  //...
  $menu->add('About', 'about');
    $menu->about->add('Goals', 'goals');
    $menu->about->add('Portfolio', 'portfolio');
 //...
 
  $about_items = $menu->about->getChildren();

});
// ...

Magic Methods

You can also search the items by magic "where" methods. These methods consist of a 'where' keyword plus a property or metadata (metadata is explained in detail below).

For example to get an item with parent equal to 12, you can use it like so:

<?php
// ...
$subs = $menu->whereParent(12);
// ...

Or to get item's with a specific meta data:

<?php
    // ...
    $menu->add('Home',     '#')->data('color', 'red');
    $menu->add('About',    '#')->data('color', 'blue');
    $menu->add('Services', '#')->data('color', 'red');
    $menu->add('Contact',  '#')->data('color', 'green');
    // ...
    
    // Fetch all the items with color set to red:
    $reds = $menu->whereColor('red');

This method returns an instance of 'Lavary\Menus\Collection'. This class has been adapted from Laravel framework.

HTML Attributes

Since all the menu items are rendered as HTML elements (like list items or divs), you can define HTML attributes for each item:

<?php

use Lavary\Menus\MenuBuilder;

$builder = new MenuBuilder();
$builder->make('MyNavBar', function($menu){

  // As you can see, you need to pass the second parameter as an associative array:
  $menu->add('Home',     array('url'  => 'home',  'class' => 'navbar navbar-home', 'id' => 'home'));
  $menu->add('About',    array('url'  => 'about', 'class' => 'navbar navbar-about dropdown'));
  $menu->add('services', 'services');
  $menu->add('Contact',  'contact');

});

If we call asUl() on the root node, the result would be something similar to this:

<ul>
  <li class="navbar navbar-home" id="home"><a href="http://yourdomain.com">Home</a></li>
  <li class="navbar navbar-about dropdown"><a href="http://yourdomain.com/about">About</a></li>
  <li><a href="http://yourdomain.com/services">Services</a></li>
  <li><a href="http://yourdomain.com/contact">Contact</a></li>
</ul>

It is also possible to set or get HTML attributes after the item has been defined by using attr() method.

attr() behaves differently based on the passed arguments. If you call attr() with one argument, it will return the attribute's value (if it's been already defined).

If you call it with two arguments, It will consider the first and second parameters as a key/value pair and sets a new attribute.

You can also pass an array of attributes to add several of HTML attributes at once (This will override all the existing attributes ).

Lastly, if you call it without any arguments, it will return all the attributes as an associative array.

<?php
    //...
    $menu->add('About', array('url' => 'about', 'class' => 'about-item'));
    
    echo $menu->about->attr('class');  // output:  about-item
    
    $menu->about->attr('class', 'another-class');
    echo $menu->about->attr('class');  // output:  about-item another-class

    $menu->about->attr(array('class' => 'yet-another', 'id' => 'about')); 
    
    echo $menu->about->attr('class');  // output:  about-item another-class yet-another
    echo $menu->about->attr('id');  // output:  id
    
    print_r($menu->about->attr());
    
    /* Output
    Array
    (
        [class] => about-item another-class yet-another
        [id] => id
    )
    */
    
    //...

You can use attr on a collection of items if you need to add the same arguments to those items at once.

<?php
// ...
$menu->add('About', 'about');

$menu->about->add('Who we are', 'about/who-we-are')->data('color', 'red');
$menu->about->add('What we do', 'about/what-we-do')->data('color', 'red');

// As mentioned earlier getChildren() returns a collection
$menu->about->getChildren()->attr('class', 'about-item');
// or
$menu->about->whereColor('red')->attr('class', 'red-text');
// ...

If you just want to add a class (or classes) to the item's class attribute, you can use addClass() method:

<?php
// ...
$menu->about-addClass('another class for about');
// ...

As a result, the passed in value will be appended to the item's existing classes.

Manipulating Links

All the HTML attributes will go to the wrapping tags (li, div, etc); You might encounter situations when you need to add some HTML attributes to <a> tags as well.

Each Lavary\Menus\Item instance has a property which stores an instance of Lavary\Menus\Link. This object is provided for you to manipulate <a> tags as an independent entity.

Just like each item, Link also has an attr() method, which functions exactly the same as item's:

<?php

use Lavary\Menus\MenuBuilder;

$builder = new MenuBuilder();
$builder->make('MyNavBar', function($menu){
//  ...
$about = $menu->add('About', ['url'  => 'about', 'class' => 'navbar navbar-about dropdown']);
$about->getLink()->attr(['class' => 'dropdown-toggle', 'data-toggle' => 'dropdown']);
// ...  
});

Link's Href Property

If you don't want to use the routing feature provided by the package, you can explicitly set your link's href property:

<?php
// ...
$menu->add('About')->getLink()->href('something/as/url');
// ...

Inserting a Separator

You can insert a separator after each item using divide() method:

<?php
//...
$menu->add('Item', 'item-url')->divide();
//...

/*
Output as <ul>:
<ul>
    ...
    <li><a href="item-url">Separated Item</a></li>
    <li class="divider"></li>
    ...
</ul>   
*/

divide() also gets an associative array of attributes:

<?php
//...
$menu->add('Separated Item', 'item-url')->divide( array('class' => 'my-divider') );
//...
    
/*
Output as <ul>:
<ul>
    ...
    <li><a href="item-url">Separated Item</a></li>
    <li class="my-divider divider"></li>

    ...
</ul>   
*/

Append and Prepend

You can append or prepend HTML or plain-text to each item's title after it is defined:

<?php

use Lavary\Menus\MenuBuilder;

$builder = new MenuBuilder();
$builder->make('MyNavBar', function($menu){
    // ...
    $about = $menu->add('About', array('url'  => 'about', 'class' => 'navbar navbar-about dropdown'));

    $menu->about->appendText(' <b class="caret"></b>')
                ->prependText('<span class="glyphicon glyphicon-user"></span> ');          
    // ...            
});

The above code will result:

<ul>
  ...
  <li class="navbar navbar-about dropdown">
   <a href="about">
     <span class="glyphicon glyphicon-user"></span> About <b class="caret"></b>
   </a>
  </li>
</ul>

You can call prependText and appendText on collections return by getChildren() or magic "where" methods as well.

Raw Items

To insert items as plain text instead of clickable links you can use raw():

<?php
// ...
$menu->raw('Item Title', array('class' => 'some-class'));  

$menu->add('About', 'about');
$menu->About->raw('Another Plain Text Item')
// ...

/* Output as an unordered list:
<ul>
  ...
  <li class="some-class">Item's Title</li>
  <li>
      About
      <ul>
          <li>Another Plain Text Item</li>
      </ul>
  </li>
  ...
</ul>
*/

Current Menu Item

You can manually set a certain menu item as the current item by using setAsCurrent() method. When an item is set as current, a group of attributes is applied to the item. These attributes - which we call them current-item-attributes - are configurable via the configuration file (more on this below).

<?php
// ...
$menu->add('Home', '#')->setAsCurrent();
// ...

/* Output
...
<li class="active"><a href="#">#</a></li>
...
*/

Menus takes care of this automatically according to the current request URI. It also adds the current-item-attributes to the item's ancestors if the current_affect_parents option is enabled in the configuration file (more on this in the Configuration section).

Matchers

Menus uses a matcher class (\Lavary\Menus\Matcher\MatcherInterface) to determine whether an item is the current one or not.

To do this, it loops through a list of patterns registered with it. Each pattern is a class implementing the interface \Lavary\Menus\Matcher\Pattern\PatternInterface.

Normally, you don't have to define a matcher when creating menus, but if you want to use your own patterns or configure the existing ones, you should instantiate the matcher and add your custom patterns to it. Then, replace the default matcher with yours, by calling setMatcher() method.

<?ph
use Lavary\Menus\MenuBuilder;
use Lavary\Menus\Matcher\Matcher;
use Lavary\Menus\Matcher\Pattern\UriPattern;

$builder = new MenuBuilder();

$matcher = new Matcher();
$matcher->addPattern(new YourCustomPattern());
$matcher->addPattern(new UriPattern('current/request/uri'));

$builder->setMatcher($matcher);

$menu->make('MyNavBar', function ($m) {
  // ...
});

You can also create your own matcher by implementing Lavary\Menus\Matcher\MatcherInterface if you need more leverage.

Patterns

As mentioned earlier, patterns are classes implementing the Lavary\Menus\Matcher\Pattern\PatternInterface

Currently, two pattern classes namely Lavary\Menus\Matcher\Pattern\UriPattern and Lavary\Menus\Matcher\Pattern\RegexPattern are provided out of the box.

Uri Pattern

You can match an item by using Lavary\Menus\Matcher\Pattern\UriPattern. This pattern matches an item if the item's URI is exactly the same as the current request URI. If you don't specify the current request URI for the pattern, the server value $_SERVER['REQUEST_URI'] will be assumed.

This is the default pattern used by the matcher.

Regex Pattern

You can also use regular expressions to match a URL. What you need to do is first, create an instance of the matcher and your add several instances of RegexPattern to it. This is useful when woking with RESTful systems.

<?php

use Lavary\Menus\MenuBuilder;
use Lavary\Menus\Matcher\Matcher;
use Lavary\Menus\Matcher\Pattern\RegexPattern;

$builder = new MenuBuilder();

$matcher = new Matcher();
$matcher->addPattern(new RegexPattern('/\/books\/(.*)/'));
$matcher->addPattern(new RegexPattern('/\/authors\/(.*)/'));

$builder->setMatcher($matcher);

$menu->make('MyNavBar', function ($m) {
  // ...
});

According to the preceding code, the matcher has two RegexPattern patterns to loop through. So all the below URIs are matched by these patterns:

  • /books/create
  • /books/12/edit
  • /authors/create
  • /authors/12

As a shortcut you can use addRegex function on the matcher to do the same thing:

<?php

use Lavary\Menus\MenuBuilder;
use Lavary\Menus\Matcher\Matcher;
use Lavary\Menus\Matcher\Pattern\RegexPattern;

$builder = new MenuBuilder();

$matcher = new Matcher();
$matcher->addRegex('/\/books\/(.*)/');
$matcher->addRegex('/\/authors\/(.*)/');

$builder->setMatcher($matcher);

// ...

As mentioned earlier, you can create custom matchers and patterns if the existing ones do not meet your requirements. All you need to do is to implement Lavary\Menus\Matcher\MatcherInterface and Lavary\Menus\Matcher\Pattern\PatternInterface interfaces respectively.

Menu Groups

One the most amazing things about Menus is menu groups. Sometimes you may need to share attributes between a group of items. Instead of specifying the attributes for each and every item, you may use a menu group.

<?php

use Lavary\Menus\MenuBuilder;

$builder = new MenuBuilder();
$builder->make('MyNavBar', function($menu){

  $menu->add('Home', array('url'  => 'home', 'class' => 'navbar navbar-home', 'id' => 'home'));
  
  $menu->group(array('style' => 'padding: 0', 'data-role' => 'navigation') function($m) {
    $m->add('About',    array('url'  => 'about', 'class' => 'navbar navbar-about dropdown'));
    $m->add('services', 'services');
  }
  
  $menu->add('Contact',  'contact');

});

According to the above code, attributes style and data-role would be applied to the items defined within the group: About and Services.

Here's the output:

<ul>
    <li class="navbar navbar-home" id="home"><a href="/home">Home</a></li>
    <li style="padding: 0" data-role="navigation" class="navbar navbar-about dropdown"><a href="/about"About</a></li>
    <li style="padding: 0" data-role="navigation"><a href="/services">Services</a></li>
    <li><a href="/contact">Contact</li>
</ul>

URL Prefixing

While using the grouping feature, we can add prefixes to be prepended to the items' URL defined within the group

<?php

use Lavary\Menus\MenuBuilder;

$builder = new MenuBuilder();
$builder->make('MyNavBar', function ($menu) {

     $menu->add('Home', array('url'  => 'home', );  // URL: /home
    $menu->add('About', array('url'  => 'about', 'class' => 'navbar navbar-about dropdown'));  // URL: /about
  
    $menu->about->group(array('prefix' => 'about'), function ($about) {
  
        $about->add('Who we are?', 'who-we-are');   // URL: about/who-we-are
        $about->add('What we do?', 'what-we-do');   // URL: about/what-we-do
    });
  
    $menu->add('Contact', 'contact');
});

The above code outputs:

<ul>
    <li><a href="/home">Home</a></li> 
    <li data-role="navigation" class="navbar navbar-about dropdown"><a href="/about/summary"About</a>
        <ul>
           <li><a href="/about/who-we-are">Who we are?</a></li>
           <li><a href="/about/who-we-are">What we do?</a></li>
        </ul>
    </li>

    <li><a href="services">Services</a></li>
    <li><a href="contact">Contact</a></li>
</ul>

Although group() method can be called against every item. However, the items defined within the group doesn't have to be children of the item that invoked group().

Nested Groups

Menus supports nested grouping as well. A menu group merges its own attribute with its parent group then shares them between the items defined within it.

<?php

use Lavary\Menus\MenuBuilder;

$builder = new MenuBuilder();
$builder->make('MyNavBar', function($menu){
    // ...  
    $menu->group(['prefix' => 'pages', 'data-info' => 'info'], function($m){       
        $m->add('About', 'about');       
        $m->group(['prefix' => 'about', 'data-role' => 'navigation'], function($a){        
            $a->add('Who we are', 'who-we-are?');
            $a->add('What we do?', 'what-we-do');
            $a->add('Our Goals', 'our-goals');
        });
    });    
});

If we render it as an unordered list:

<ul>
    <li data-info="info">
        <a href="/pages/about">About</a>
        <ul>
            <li data-info="info" data-role="navigation"><a href="/pages/about/who-we-are"></a></li>
            <li data-info="info" data-role="navigation"><a href="/pages/about/what-we-do"></a></li>
            <li data-info="info" data-role="navigation"><a href="/pages/about/our-goals"></a></li>
        </ul>
    </li>
</ul>

Important: Please note that if an item or group already has the attribute it will override its parent's. Only the attributes prefix and class are appended to the parent's value.

Metadata

You might encounter situations when you need to assign some metadata to each item; This data can be for any use case from item's weight for sorting to item's or required permissions to display the items.

To assign data to an item, you can use data() method.

If you call data() with one argument, it will return the data value for you if it's already been defined.

If you call it with two arguments, It will consider the first argument as the key and second argument as the value.

You can also pass an associative array of data if you need to add a group of key/value pairs at once (This will override all the existing metadata).

Lastly, if you call it without any arguments. it will return all data as an array.

<?php

use Lavary\Menus\MenuBuilder;

$builder = new MenuBuilder();
$builder->make('MyNavBar', function($menu){

  // ...
  
  $menu->add('Users', array('url' => 'users'))
       ->data('permission', 'manage_users');

});

Or to access it:

<?php
// ...
var_dump($menu->users->data('permission'))
// ...  

A meta data doesn't do anything to the item's behavior on its own, and it won't be rendered in HTML either. It is the developer who decides what to do with it.

You can also call data() on a collection returned by getChildren() or magic "where" method, if you need to target a group of items:

<?php
// ...
$menu->add('Users', 'users');
$menu->users->add('New User', 'users/new');
$menu->users->add('Uses', 'users');

// add a meta data to children of Users
$menu->users->children()->data('anything', 'value'); 
// ...

Meta data can also be used for sorting or filtering the items (More on this below).

Cascading Data

You can also cascade this meta to the item's children. You only need to make sure cascade_data option is enabled in the configuration file (it is enabled by default). As a result, whenever you add a metadata entry to an item, the existing descendants will inherit it.

Very Important Note: The cascading process takes place when a metadata (or a group of metadata) is added to an item with children (if cascading is enabled). This means that it only affects the children created before the cascading process; The children created after the cascading won't inherit the metadata. The reason is that you might want to keep some metadata only for the parent while having the cascading feature enabled as well. So, if cascading is enabled and you want to add some metadata only for the parent, add them before you create any sub-items.

Filtering the Items

You can filter menu items by a using filter() method. Filter() accepts either a key/value pair or a closure.

The following example will filter the items based on metadata show.

<?php

use Lavary\Menus\MenuBuilder;

$builder = new MenuBuilder();
$builder->make('MyNavBar', function($menu){

    $menu->add('Home', 'home')->data('show', true);
    $menu->add('About', 'about')->data('show', true);
    $menu->add('Services', 'services')->data('show', true);
    $menu->add('Portfolio', 'portfolio')->data('show', false);
    $menu->add('Contact', 'contact')->data('show', true);
    
})->filter('show', true);

According to the above code, the value of show must be true in order to be included in the filter result.

If you want to pass a closure to filter(), you must return false for the items you want to exclude and true for those you want to keep.

Here's a basic example:

Let's just imagin the User model can check whether the user has an specific permission or not:

<?php

use Lavary\Menus\MenuBuilder;

$builder = new MenuBuilder();
$builder->make('MyNavBar', function($menu){
  // ...
  $menu->add('Users', array('url'  => 'admin/users'))
       ->data('permission', 'manage_users');

})->filter(function($item){
  if(User::get()->can( $item->data('permission'))) {
      return true;
  }
  return false;
});

As a result, Users item will be visible to those who has the manage_users permission. As you probably have noticed we attached the required permission for each item via data() method.

Sorting the Items

You can sort the items by using SortBy() method. sortBy can sort items by a certain metadata or a user-defined function.

To sort the items based a metadata:

<?php

use Lavary\Menus\MenuBuilder;

$builder = new MenuBuilder();
$builder->make('main', function ($menu) {
    $menu->add('About', '#')     ->data('order', 2);
    $menu->add('Home', '#')      ->data('order', 1);
    $menu->add('Services', '#')  ->data('order', 3);
    $menu->add('Contact', '#')   ->data('order', 5);
    $menu->add('Portfolio', '#') ->data('order', 4);
})->sortBy('order');

In the above code, the items are sorted in ascending order by order's value.

sortBy() also receives a second parameter which specifies the ordering type: ascending order asc and descending order desc.

The default value is asc.

You can also sort items by passing a callback function:

<?php

use Lavary\Menus\MenuBuilder;

$builder = new MenuBuilder();
$builder->make('main', function($m){

    $m->add('About')     ->data('order', 2);
    $m->add('Home')      ->data('order', 1);
    $m->add('Services')  ->data('order', 3);
    $m->add('Contact')   ->data('order', 5);
    $m->add('Portfolio') ->data('order', 4);

})->sortBy(function($items) {
    // Your sorting algorithm here...
});     

The closure takes the root item as argument.

Configuration

You can adjust the behavior of the menu builder in the provided configuration file (menu.yml). By default, the configuration file shipped with the package is used by the menu builder. To customize the file, you better make a copy of the configuration file, then customize the settings. You can find the file in the package's root directory.

Here's the list of options you can modify inside the file:

Option Name Description Possible Values Default Value
cascade_data If you need descendants of an item to inherit metadata from their parents, make sure this option is enabled Boolean True
auto_activate Automatically set an item as current if its URI matches to one of the defined patterns Boolean True
current_affect_parents Set the ancestors of the current item as current Boolean True
active_attributes A list of attributes to be added to the current item YAML options class: active
active_element You can choose the HTML element to which you want to add activation attributes (anchor or the wrapping element). item or link item

To use your own configuration file you can create an instance of \Lavary\Menus\Configuration\Configuration and pass it the MenuBuilder class constructor.

<?php

use Lavary\Menus\MenuBuilder;
use Lavary\Menus\Configuration\Configuration;

$builder = new MenuBuilder(new Configuration('/path/to/menu.yml'));

// ...

Alternatively you can use setConfiguration():

<?php

use Lavary\Menus\MenuBuilder;
use Lavary\Menus\Configuration\Configuration;

$builder = new MenuBuilder();
$builder->setConfiguration(new Configuration('/path/to/menu.yml'));
// ...