Like @seomike, I prefer using a view composer than anything else. However, I don't like hard-coding that logic into a view composer. Therefore, I opted to take a bit of a hybrid approach. What I did was:
- Create a
ViewComposerServiceProvider as explained in the 5.3 docs on View Composers.
- Update the
app\config.php to load the new service provider.
- Create an
ActiveLinkComposer, as illustrated in the PHP snippet below.
- Configure my
ViewComposerServiceProvider to load the ActiveLinkComposer
- Use the
activeRoute and activePath methods in my views, as illustrated in the blade snippet below.
Active Link Composer
<?php
namespace App\Services\ViewComposers;
use Illuminate\View\View;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
class ActiveLinkComposer
{
/**
* @var \Illuminate\Http\Request
*/
private $request;
/**
* @var string
*/
private $activeClass = 'active';
/**
* @var string
*/
private $template = ' class="{classes}"';
/**
* Create a new profile composer.
*
* @param \Illuminate\Http\Request $request
* @return void
*/
public function __construct(Request $request)
{
$this->request = $request;
}
/**
* Bind data to the view.
*
* @param View $view
* @return void
*/
public function compose(View $view)
{
$view->with('link', $this);
}
/**
* Determine if a link should be marked as active based on the provided route(s)
* and return the class attribute. Optionally, you can override the template
* to only return the class list or individual active state for the link.
*
* @param $routes string|array A single or list of routes to match against.
* @param string $classes string A string of classes to be returned with the state.
* @param null $activeClass string A string to override the $this->activeClass.
* @param null $template string A string to override $this->template
* @return string
*/
public function activeRoute($routes, $classes = '', $activeClass = null, $template = null)
{
return $this->buildClassList(
$routes, $this->request->route()->getName(), $classes, $activeClass, $template
);
}
/**
* Determine if a link should be marked as active based on the provided path(s)
* and return the class attribute. Optionally, you can override the template
* to only return the class list or individual active state for the link.
*
* @param $paths string|array A single or list of paths to match against.
* @param string $classes string A string of classes to be returned with the state.
* @param null $activeClass string A string to override the $this->activeClass.
* @param null $template string A string to override $this->template
* @return string
*/
public function activePath($paths, $classes = '', $activeClass = null, $template = null)
{
return $this->buildClassList(
$paths, $this->request->decodedPath(), $classes, $activeClass, $template
);
}
/**
* Determine if a link should be marked as active based on provided patterns(s)
* and return the class attribute. Optionally, you can override the template
* to only return the class list or individual active state for the link.
*
* @param $patterns string|array A single or list of patterns to match against.
* @param $value string The string to match the patterns against.
* @param string $classes string A string of classes to be returned with the state.
* @param null $activeClass string A string to override the $this->activeClass.
* @param null $template string A string to override $this->template
* @return string
*/
private function buildClassList($patterns, $value, $classes, $activeClass, $template)
{
if ($this->matches($patterns, $value)) {
$classes = trim(implode(' ', [$classes, $activeClass ?: $this->activeClass]));
}
return str_replace('{classes}', $classes, $template ?: $this->template);
}
/**
* Determine if any of the provided patterns match the given value.
*
* @param $patterns string|array A single or list of patterns to match against.
* @param $value string The string to match the patterns against.
* @return bool
*/
private function matches($patterns, $value)
{
foreach ((array) $patterns as $pattern) {
if (Str::is($pattern, $value)) {
return true;
}
}
return false;
}
/**
* Set the active class to be used on future calls.
*
* @param $activeClass
* @return $this
*/
public function class($activeClass)
{
$this->activeClass = $activeClass;
return $this;
}
/**
* Set the template to be used on future calls.
*
* @param $activeTemplate
* @return $this
*/
public function template($activeTemplate)
{
$this->template = $activeTemplate;
return $this;
}
}
Blade Template
(Do note that they should be used in {!! !!} tags to avoid being escaped when in HTML. All examples assume I'm on setup/requirements and it's named setup.requirements)
{{-- Single path wildcard, matches. Returns: class="active" --}}
{{ $link->activePath('setup/*') }}<br>
{{-- Single path, matches. Includes other classes. Returns: class="link-item active" --}}
{{ $link->activePath('setup/requirements', 'link-item') }}<br>
{{-- Multiple paths, matches. Overrides active class. Returns: class="link-item active-item" --}}
{{ $link->activePath(['setup/*', 'mismatch/path'], 'link-item', 'active-item') }}<br>
{{--Single route wildcard, matches. Overrides template. Returns: link-item active--}}
{{ $link->activeRoute('*.requirements', 'mismatch.route', 'link-item', null, '{classes}') }}<br>
{{-- Single route, matches. Sets new default active class and template. Returns: data-class="link-item activated" --}}
{{ $link->class('activated')->template(' data-class="{classes}"')->activeRoute('setup.*', 'link-item') }}<br>
{{-- Multiple routes, matches. Uses above template and class. Returns: data-class="link-item active-item" --}}
{{ $link->activeRoute(['setup.requirements', 'mismatch.route'], 'link-item', 'active-item') }}<br>
{{-- Multiple routes, matches. Uses above template and class. Returns: data-class="link-item active-item" --}}
{{ $link->activeRoute(['setup.requirements', 'mismatch.route'], 'link-item', 'active-item') }}<br>
{{-- Multiple routes, doesn't match. Returns: ng-class="link-item" --}}
{{ $link->activePath(['requirements', 'mismatch.route'], 'link-item', 'active-item', ' ng-class="{classes}"') }}<br>
So basically it's pretty short and sweet but yet is flexible for those edge cases where you just need a bit more control. You can also wrap your links in a separate template and @include('partials.link', ['var' => $val, …])them with variables passed as additional arguments to try and keep your templates a bit cleaner.