laravel-nestedset bootstrap navbar extension

Submitted by subdesign - 9 years ago

If you know what nested sets are, and use lazychazer/laravel-nestedset package, and tried his example application (https://github.com/lazychaser/nestedset-app), then here is a snippet for replace his simple nav rendering to bootstrap (3.x) navbar compatible output. (Laravel 5 solution)

// 1. the navbar code, just the same example as on the getbootstrap.com site

<nav class="navbar navbar-default">
  <div class="container-fluid">
    <!-- Brand and toggle get grouped for better mobile display -->
    <div class="navbar-header">
      <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      <a class="navbar-brand" href="#">Brand</a>
    </div>

    <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
      <ul class="nav navbar-nav">

        {!! HTML::navbar($nav) !!} <!-- $nav is the result of make_nav() function -->

      </ul>      
      <form class="navbar-form navbar-left" role="search">
        <div class="form-group">
          <input type="text" class="form-control" placeholder="Search">
        </div>
        <button type="submit" class="btn btn-default">Submit</button>
      </form>
      <ul class="nav navbar-nav navbar-right">
        <li><a href="#">Link</a></li>
        <li class="dropdown">
          <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">Dropdown <span class="caret"></span></a>
          <ul class="dropdown-menu" role="menu">
            <li><a href="#">Action</a></li>
            <li><a href="#">Another action</a></li>
            <li><a href="#">Something else here</a></li>
            <li class="divider"></li>
            <li><a href="#">Separated link</a></li>
          </ul>
        </li>
      </ul>
    </div><!-- /.navbar-collapse -->
  </div><!-- /.container-fluid -->
</nav> 

// 2. add one line to make_nav() function

function make_nav(Collection $tree, $activeItemKey = null, &$active = null)
{
    if (!$tree->count()) return null;

    return array_map(function ($item) use ($activeItemKey, &$active) {
        
        $data = array();

        $childActive = false;

        $data['items'] = make_nav($item->children, $activeItemKey, $childActive);

        if ($activeItemKey !== null) 
        {
            $childActive |= $activeItemKey == $item->getKey();
        }

        $active |= $childActive;

        $data['active'] = $childActive;

        $data['parent_id'] = $item->parent_id; // this line is added

        foreach (array('url', 'label') as $key) {

            $getter = 'getNav'.ucfirst($key);

            $data[$key] = $item->$getter();
        }

        return $data;

    }, $tree->all());
}

// 3. the HTML macro, which helps you build the navbar menu system

HTML::macro('navbar', function($data)
{
    if (empty($data)) return '';

    $html = '';

    foreach ($data as $item)        
    {
        if (is_null($item['parent_id'])) {
            $html .= HTML::navbar($item['items']);  
        } else {

            $html .= '<li class="';

            if (isset($item['active']) && $item['active']) {        
                $html .= ' active';
            }
            
            if (isset($item['items'])) {
                $html .= ' dropdown';
            }        

            $html .= '"';
            
            if (isset($item['items'])) {
                $html .= '><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">';
            } else {
                $html .= '><a href="'.$item['url'].'">';
            }

            if (isset($item['items'])) {
                $html .= e($item['label']).' <span class="caret"></span></a>';
            } else {
                $html .= e($item['label']).'</a>';
            }

            if (isset($item['items'])) {
                $html .= '<ul class="dropdown-menu" role="menu">';                
                $html .= HTML::navbar($item['items']);  
                $html .= '</ul>';
            } 

            $html .= '</li>';
        }
    }   

    return $html;
    
});

// Question: where to put the file containing macros in Laravel 5?
// Answer: create a service provider like this:

<?php namespace App\Providers;

use Illuminate\Html\HtmlServiceProvider;

class MacroServiceProvider extends HtmlServiceProvider {

 	public function register()
	{
		parent::register();
		require base_path().'/app/Macros/macros.php';
	}

}

// Question: why the first menu item is skipped?
// Answer: with this package the first root item is the ancestor, every other item will be it's children, so the Bootstrap menu would be ugly..

// Note: it works only for one depth as the default Bootstrap navbar menu