Best practice for handling "active" menu item in L5

Published 3 years ago by jason

I've been trying to wrap my head around how best to address marking a menu item as the current page (I'm using twitter bootstrap for front-end css). In L4 it is suggested to do something like this:

<li class="{{ Request::is('home') ? 'class="active"' : '' }}"><a href="{{URL::to('clients')}}">Home</a></li>

This doesn't seem to work in L5 though.

In attempt to stay DRY, I implemented a Nav helper class with the following method:

    public static function setActive($path, $request, $active = 'active')
    {
        return $request->is($path) ? $active : '';
    }

in my blade template I can do this:

<li class="{{\Webapp\Helpers\Nav::setActive('home', $request)}}">

but I will have to return the request in my controllers for every route that I want to enable for active menu navigation:

return view('home', compact('request'));

I swear I saw a laracast that touched on this subject but am having trouble finding it again.

Is returning the request in every controller method the best way to handle active menu items? Is there a more elegant solution?

EDIT: found the laracast I was looking for but would still like to get feedback from others on solutions that work well in L5:

https://laracasts.com/lessons/active-states

DaRo
DaRo
2 years ago (2,300 XP)

This is really old, but i've found a solution that worked for me:

I did the menu list in bootstrap way, and it would be like this;

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

<li {{{ (Request::is('dashboard') ? 'class=active' : '') }}}>
<a href="{{action('action to see the view')}}">Home</a>
</li>

<li {{{ (Request::is('companies') ? 'class=active' : '') }}}>
<a href="{{action('action to see the view')}}">Companies</a>
</li>
</ul>

</div><!--/.nav-collapse -->

Notist that the thing that you put inside the request should be the path, in my case, for the dasboard was project.dev/dashboard

Hope this helps someone.

kfirba
kfirba
2 years ago (219,245 XP)

@jason If you want a js solution I have built a very small script to do that:

https://github.com/kfirba/mavigator

bestmomo
bestmomo
2 years ago (378,360 XP)

I've created 2 helpers :

if (!function_exists('classActivePath')) {
    function classActivePath($path)
    {
        return Request::is($path) ? ' class="active"' : '';
    }
}

if (!function_exists('classActiveSegment')) {
    function classActiveSegment($segment, $value)
    {
        if(!is_array($value)) {
            return Request::segment($segment) == $value ? ' class="active"' : '';
        }
        foreach ($value as $v) {
            if(Request::segment($segment) == $v) return ' class="active"';
        }
        return '';
    }
}

And I use it as this :

<li {!! classActivePath('medias') !!}>
    <a href="{!! route('medias') !!}"><span class="fa fa-fw fa-file-image-o"></span> {{ trans('back/admin.medias') }}</a>
</li>
<li {!! classActiveSegment(1, 'blog') !!}>
    <a href="#" data-toggle="collapse" data-target="#articlemenu"><span class="fa fa-fw fa-pencil"></span> {{   
    ...
</li>
mindthebrand

It is more versatile.

if (!function_exists('classActivePath')) {
    function classActivePath($path)
    {
        $path = explode('.', $path);
        $segment = 1;
        foreach($path as $p) {
            if((request()->segment($segment) == $p) == false) {
                return '';
            }
            $segment++;
        }
        return ' active';
    }
}
 <ul>
    <li class="{!! classActivePath('order') !!}">
        <a href="#">Order</a>
        <ul>
            <li class="{!! classActivePath('order.index') !!}">
                <a href="{{ route('order.index') }}"> Orders</a>
            </li>
            <li class="{!! classActivePath('order.index') !!}">
                <a href="{{ route('order.create') }}"> Order Create</a>
            </li>
        </ul>
    </li>
</ul>  
pmall
pmall
2 years ago (583,445 XP)

I send $request->segment(1) through a view composer as an $active variable.

Then <li class="{ { $active == 'posts' ? 'active' : '' } }">posts</li>.

edvinaskrucas
JoshWegener

What I decided to go with....

if (!function_exists('menuActiveRoute')) {
    function menuActiveRoute($routeName, $includeClass = true, $className = 'active') {
        return preg_match('/^' . preg_quote($routeName, '/') . '/', \Request::route()->getName()) ? ($includeClass ? "class=\"{$className}\"": $className): '';
    }
}

And a longer version if you prefer...

if (!function_exists('menuActiveRoute')) {
    function menuActiveRoute($routeName, $includeClass = true, $className = 'active') {
        $activeClass = ($includeClass ? "class=\"{$className}\"": $className);
        return preg_match('/^' . preg_quote($routeName, '/') . '/', \Request::route()->getName()) ? $activeClass: '';
    }
}
consil
consil
2 years ago (14,520 XP)

I've seen this package used by a few demos, and and trying it out myself:

https://github.com/laravelista/Ekko

So far, it's working well, but it needs a wildcard or pattern match for the route name, for which I'm working on a PR.

Edit: Ekko now supports wildcard path names and multiple path names. This is absolutely essential for my use-case as some parts of my admin structure are four levels deep (since it models some real-life structures that need to be set up). This allows multiple sub-menus to activate the top-level menu that they are under. The wildcards then support resource-based URLs.

This is my top-level side-menu option for a "show", which lights up when any of about 30 pages under it are being used:

<li class="{{ Ekko::areActiveRoutes(['admin.shows.*', 'admin.shows.groups.*', 'admin.shows.groups.judges.*', 'admin.shows.products.*', 'admin.shows.orders.*']) }}">
    <a href="{{ route('admin.shows.index') }}">Shows</a>
</li>

My aims in choosing this were:

  • Not to reinvent this common requirement yet again. Yes, the 80% use-case might be quick and simple, but you will quickly hit limitations once your application becomes a little more complex.
  • Choose an open source package that gets most of the way and offer PRs to improve it to better fit my use-case (for both me and others in future projects).
  • To minimise PHP in the HTML markup - conditions, ternary operator etc. Just calling a function was the most I wanted to put into the markup.
  • To match against route names rather than paths. The path stays in routes.php in my applications and ALL other parts of the application just refer to route names. That way paths can be moved and juggled around as the application and its needs grow, without the need to find every instance that references a path.

On that last point, Ekko does support path matches too, so it's there if you want to use it.

kel
kel
2 years ago (17,530 XP)

I know this is old but here is what I did as a solution for me. This checks it by path and allows multiple paths as well as additional class pass through.

function active_class_path($paths, $classes = null)
{
    foreach ((array) $paths as $path) {
        if (request()->is($path)) {
            return 'class="' . ($classes ? $classes . ' ' : '') . 'active"';
        }
    }
    return $classes ? 'class="' . $classes . '"' : '';
}

And an example here

<ul class="menu">
    <li><a href="{{ url('articles') }}" {!! active_class_path(['articles', 'a/*']) !!}>Articles</a></li>
    <li><a href="{{ url('dashboard') }}" {!! active_class_path('dashboard', 'additional classes here') !!}>Dashboard</a></li>
    <li><a href="{{ url('dashboard/users') }}" {!! active_class_path('dashboard/users') !!}>Users</a></li>
    <li><a href="{{ url('dashboard/settings') }}" {!! active_class_path('dashboard/settings') !!}>Site settings</a></li>
</ul>
vipindasks

This is how I do it. In my controller I set a name and share it to the view

<?php namespace App\Http\Controllers\Admin;

use Auth;
use Validator;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;

class MyController extends Controller
{
    public function __construct()
    {
        View::share(['page_name_active'=> 'mypagename']);       
    }
    /* rest of logic */
}

And in my view, I check against the respective values and show active

                        <li class="@if($page_name_active=='mypagename')active @endif">
                            <a href="{{route('admin.mypage')}}" >My Page</a>
                        </li>
Gerard
Gerard
2 years ago (17,100 XP)

Damn... I have this working

if (! function_exists('is_current_route')) {
    function is_current_route($route){
        return Request::url() == $route ? 'class=active' : '';
    }
}

but when I change it to Request::is it doesn't work anymore :S

if (! function_exists('is_current_route')) {
    function is_current_route($route){
        return Request::is($route) ? 'class=active' : '';
    }
}
silveragency

This is how I add active classes. I first create a functions file app/Helpers/functions.php and add this function:

    /* Set active class
    -------------------------------------------------------- */
    function set_active($path, $active = 'active') {
        return call_user_func_array('Request::is', (array)$path) ? $active : '';
    }

I then add this to autoload in composer.json

    "autoload": {
        "classmap": {...},
        "psr-4": {...},
        "files": [
            "app/Helpers/functions.php"
        ]
    },

Then run composer dumpautoload.

And then use the function like so:


    <ul>
        <li class="dropdown {{ set_active(['page', 'page/*']) }}">
            <a href="#" class="dropdown-toggle" data-toggle="dropdown">Page</a>
            <ul class="dropdown-menu" role="menu">
                <li class="{{ set_active(['page/child-1']) }}">
                    <a href="{{ url('page/child-1') }}">Child 1</a>
                </li>
                <li class="{{ set_active(['page/child-2']) }}">
                    <a href="{{ url('page/child-2') }}">Child 2</a>
                </li>
                <li class="{{ set_active(['page/child-3']) }}">
                    <a href="{{ url('page/child-3') }}">Child 3</a>
                </li>
            </ul>
        </li>
    </ul>

This will add the active class to .dropdown for the routes: /page and any child routes /page/*

e.g /page/child-1, /page/child-2, /page/child-3

and will also add the active class to each relevant child page.

Khudadad

I 'm doing this like bellow : in my controller I declare $menu = '...'; and <$sub_menu = '....'; and check it in the views.

 <li class="<?php if ($menu == 'purchase') echo "active open dropdown"; ?>">
                                <a role="button" tabindex="0"><i class="fa fa-shopping-cart"></i> <span>Purchase Management</span> <span class="badge bg-lightred"></span></a>
                                <ul>
                                    <li class="<?php if ($sub_menu == 'purchase_list') echo "active"; ?>"><a href="/purchase"><i class="fa fa-caret-right"></i> Purchase List</a></li>
                                    <li class="<?php if ($sub_menu == 'add_new_purchase') echo "active"; ?>"><a href="/purchase/add" ><i class="fa fa-caret-right"></i> Add New Purchase</a></li>
                                </ul>
                            </li>
kickthemooon

I am solving it by passing variables from my controllers.

public function index(){
        $data=array('menuParent'=>'Users', 'menuChild'=>'viewUsers');
        $users=User::all();
        return view('admin.users.index', $data)->with(compact('users'));
    }

view

<li class="treeview @if(isset($menuParent)) {{ ($menuParent=='Users') ? 'active' : '' }} @endif">
                <a href="#">
                    <i class="fa fa-user"></i> <span>Users</span>
                    <i class="fa fa-angle-left pull-right"></i>
                </a>
                <ul class="treeview-menu">
                    <li @if(isset($menuChild)) {{ ($menuChild=='viewUsers') ? "class=active" : "" }} @endif>
                        <a href="/admin/users"><i class="fa fa-circle-o"></i> Users overview</a>
                    </li>
                    <li @if(isset($menuChild)) {{ ($menuChild=='addUser') ? "class=active" : "" }} @endif>
                        <a href="/admin/user/add"><i class="fa fa-circle-o"></i> Add User</a>
                    </li>
                </ul>
            </li>
seomike
seomike
1 year ago (14,095 XP)

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!

Please sign in or create an account to participate in this conversation.