seomike

Bootstrap Menus With Dynamic active <li> tags

SEO Friendly solution

Solved this using View Composers

  • No, Javascript needed
  • No, Extra static lists needed
  • No, Messy IF ELSE logic in your views
  • No, Messy controllers at all
  • No, 3rd party package needed

Routes

routes/web.php

This uses named routes to as a means to generate the <li> tags with <a> links for Bootstrap <ul> navs using View Composers.

// Routes
Route::group(['middleware' => 'auth','prefix' => 'foo'], function ($router) {

    // Side Menu
    // The view composer will strip foo.sidenav. off the route name and use dashboard to create the link text in the <a>
    Route::get('/dashboard', ['as' => 'foo.sidenav.dashboard', 'uses' => '[email protected]']);
    // The view composer will strip foo.sidenav. off the route name and use settings to create the link text in the <a>
    Route::get('/settings', ['as' => 'foo.sidenav.settings', 'uses' => '[email protected]']);

    // Top Menu
    Route::get('/profile', ['as' => 'foo.topnav.profile', 'uses' => '[email protected]']);    

});

Config

config/app.php

Register the service provider

/*
         * Application Service Providers...
         */
        App\Providers\AppServiceProvider::class,
        // App\Providers\BroadcastServiceProvider::class,
        App\Providers\AuthServiceProvider::class,
        App\Providers\ComposerServiceProvider::class,  // << add this composer service provider
        App\Providers\EventServiceProvider::class,
        App\Providers\RouteServiceProvider::class,

Build the Service Provider

app/Http/Providers/ComposerServiceProvider.php

<?php

namespace App\Providers;

use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;

class ComposerServiceProvider extends ServiceProvider
{
    /**
     * Register bindings in the container.
     *
     * @return void
     */
    public function boot()
    {
        // Using class based composers...
    // We use the wild card matching to make sure this composer
    // is used on views called  to routes prefixed with /foo (/foo/dashboard /foo/settings)
    // change 'foo.*', '*' if you want all views to have access
        View::composer('foo.*', 'App\Http\Composers\SideNavComposer'
        );

    }

}

Create the Composer

app/Http/Composer/SideNavComposer.php

Now create the actual composer ;)

<?php

namespace App\Http\Composers;

use Illuminate\Http\Request;
use Illuminate\Routing\Router;
use Illuminate\View\View;

class SideNavComposer
{
    /**
     * @var Router
     */
    protected $router;

    /**
     * @var Request
     */
    protected $request;

    /**
     * Route name prefix / for matching
     * This will cause the composer to only target named routes that begin with foo.sidenav
     * @var string
     */
    protected $prefix = 'foo.sidenav.';

    /**
     * Create a new SideNav composer.
     *
     * @param Illuminate\Routing\Router $router
     * @param Illuminate\Http\Request $request
     */
    public function __construct(Router $router, Request $request)
    {
        // Ooooh baby gimme those routes
        $this->router = $router;
    
    // ... And the current request too
        $this->request = $request;
    }

    /**
     * Bind data to the view.
     *
     * @param  View $view
     * @return void
     */
    public function compose(View $view)
    {
        // Format links
        $list = $this->createListItems();

    // WHERE THE MAGIC HAPPENS
        // return $sidenav array with any views that are called
        $view->with('sidenav', $list);
    }

    /**
     * Create <li> list items w/ <a> as per composer parameters
     *
     * @return array
     */
    protected function createListItems()
    {
        $list = [];
    
    // Loop through the routes
        foreach ($this->router->getRoutes() as $route) {

            if (strstr($route->getName(), $this->prefix)) {

                // Activate current link ? leave blank
                $active = ($route->uri() == $this->request->path()) ? ' class="active"' : '';
        // Create URI
                $uri = '/' . $route->uri();
        // Create link text 
                $text = $this->makeLinkText($route->getName());

                // Push list item
                $list[] = '<li' . $active . '><a href="' . $uri . '">' . $text . '</a>';
            }

        }

        return $list;

    }

    /**
     * Make link text for <a> tag
     *
     * @param $name
     * @return string
     */
    protected function makeLinkText($name)
    {
        return ucfirst(str_replace($this->prefix, '', $name));
    }
}

View Files

resources/views/layouts/app.blade.php

<!DOCTYPE html>
<html lang="en">
<head><link href="{{elixir('css/app.css')}}" rel="stylesheet"></head>
<body>
<div class="container-fluid">

    <div class="row">

        <!-- Left nav -->
        <div class="col-md-2">
            <div class="panel panel-default">
                
        <div class="panel-body">
            <ul class="nav nav-pills nav-stacked">
                
                <!-- SideNavComposer ready for use -->
                    @foreach($sidenav as $list)
                        {!! $list !!}
                    @endforeach 
            <!-- Holy Sh!t that's clean ;) -->

            </ul>
                </div>

            </div>

        </div>

        <!-- Content -->
        <div class="col-md-10">
            <div class="panel panel-default">
                <div class="panel-body">

                    @yield('content')

                </div>
            </div>

        </div>

    </div>

</div>

<!-- Scripts -->
<script src="/js/app.js"></script>
</body>
</html>

resources/views/dashboard.blade.php

@extends('layouts.app')

@section('content')
    Some Dashboard contentDashboard
@endsection

Controller

app/Http/Controllers/DashboardController.php

<?php

namespace App\Http\Controllers;

class DashboardController extends Controller
{
    /**
     * Show the application dashboard.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        return view('dashboard');
    }
}

Want a new sidemenu link. Easy Just add a new route prefixed with foo.sidenav.

<?php
Route::group(['middleware' => 'auth','prefix' => 'foo'], function () {
    //...   
    Route::get('/bar', ['as' => 'foo.sidenav.bar', 'uses' => '[email protected]']);
    Route::get('/cancel', ['as' => 'foo.sidenav.cancel', 'uses' => '[email protected]']);
    Route::get('/billing', ['as' => 'foo.sidenav.billing', 'uses' => '[email protected]']);
});

Now make a Composer for top nav or bottom nav etc. Enjoy!

Return to Thread...