Laravel Nova Customize Navigation
Tags: laravel, nova, navigation
Published: 2021-03-31 00:49:31

Laravel Nova is a great tool! There are many packages that will do similar to the customization I will introduce here. 

As a dev, less package is always better if the required customization is simple. 

So, This is what we will customize

1. Add an icon to each navigation 
2. Add method to define return the sort value so we can sort
3. Both navigation and group use the defined sort value for sorting



 If you open a default `navigation.blade.php` it looks something like this
```
@if (count(\Laravel\Nova\Nova::resourcesForNavigation(request())))
    <h3 class="flex items-center font-normal text-white mb-6 text-base no-underline">
        <svg class="sidebar-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
            <path fill="var(--sidebar-icon)" d="M3 1h4c1.1045695 0 2 .8954305 2 2v4c0 1.1045695-.8954305 2-2 2H3c-1.1045695 0-2-.8954305-2-2V3c0-1.1045695.8954305-2 2-2zm0 2v4h4V3H3zm10-2h4c1.1045695 0 2 .8954305 2 2v4c0 1.1045695-.8954305 2-2 2h-4c-1.1045695 0-2-.8954305-2-2V3c0-1.1045695.8954305-2 2-2zm0 2v4h4V3h-4zM3 11h4c1.1045695 0 2 .8954305 2 2v4c0 1.1045695-.8954305 2-2 2H3c-1.1045695 0-2-.8954305-2-2v-4c0-1.1045695.8954305-2 2-2zm0 2v4h4v-4H3zm10-2h4c1.1045695 0 2 .8954305 2 2v4c0 1.1045695-.8954305 2-2 2h-4c-1.1045695 0-2-.8954305-2-2v-4c0-1.1045695.8954305-2 2-2zm0 2v4h4v-4h-4z"
            />
        </svg>
        <span class="sidebar-label">{{ __('Resources') }}</span>
    </h3>

    @foreach($navigation as $group => $resources)
        @if (count($groups) > 1)
            <h4 class="ml-8 mb-4 text-xs text-white-50% uppercase tracking-wide">{{ $group }}</h4>
        @endif

        <ul class="list-reset mb-8">
            @foreach($resources as $resource)
                <li class="leading-tight mb-4 ml-8 text-sm">
                    <router-link :to="{
                        name: 'index',
                        params: {
                            resourceName: '{{ $resource::uriKey() }}'
                        }
                    }" class="text-white text-justify no-underline dim" dusk="{{ $resource::uriKey() }}-resource-link">
                        {{ $resource::label() }}
                    </router-link>
                </li>
            @endforeach
        </ul>
    @endforeach
@endif
```

1. Add an icon to each navigation 

Let's look at the part where each link is render 
```
@foreach($resources as $resource)
                <li class="leading-tight mb-4 ml-8 text-sm">
                    <router-link :to="{
                        name: 'index',
                        params: {
                            resourceName: '{{ $resource::uriKey() }}'
                        }
                    }" class="text-white text-justify no-underline dim" dusk="{{ $resource::uriKey() }}-resource-link">
                        {{ $resource::label() }}
                    </router-link>
                </li>
            @endforeach
```


If you `dd($resource)` it's a fully FQN of your resources. And this on each, it's calling static method to resource class to get the label
```
{{ $resource::label() }}
```

We are going to add a new `icon()` method to the resource. 
```
    /**
     * The icon of the resource.
     *
     * @return string
     */
    public static function icon()
    {
        return view('nova::icons.icon-customer')->render();
    }
```
in the `icon-customer.blade.php` file we only have an SVG. 
```
<svg width="20" height="20" class="sidebar-icon" fill="var(--sidebar-icon)" viewBox="0 0 25 26"  xmlns="http://www.w3.org/2000/svg"><path d="M18.501 15.62a7.161 7.161 0 003.409-6.095c0-3.949-3.202-7.161-7.137-7.161-3.935 0-7.137 3.212-7.137 7.161a7.161 7.161 0 003.408 6.095c-4.406.992-7.499 3.702-7.499 6.98 0 2.684 7.058 3.4 11.228 3.4S26 25.284 26 22.6c0-3.278-3.092-5.988-7.499-6.98zM9.106 9.525c0-3.134 2.542-5.684 5.667-5.684 3.124 0 5.666 2.55 5.666 5.684 0 3.135-2.542 5.684-5.666 5.684-3.125 0-5.667-2.55-5.667-5.684zm5.667 14.998c-5.994 0-9.582-1.27-9.757-1.927.005-3.259 4.38-5.91 9.757-5.91 5.376 0 9.75 2.65 9.756 5.909-.17.656-3.758 1.928-9.756 1.928z" /><path d="M18.501 15.62a7.161 7.161 0 003.409-6.095c0-3.949-3.202-7.161-7.137-7.161-3.935 0-7.137 3.212-7.137 7.161a7.161 7.161 0 003.408 6.095c-4.406.992-7.499 3.702-7.499 6.98 0 2.684 7.058 3.4 11.228 3.4S26 25.284 26 22.6c0-3.278-3.092-5.988-7.499-6.98zM9.106 9.525c0-3.134 2.542-5.684 5.667-5.684 3.124 0 5.666 2.55 5.666 5.684 0 3.135-2.542 5.684-5.666 5.684-3.125 0-5.667-2.55-5.667-5.684zm5.667 14.998c-5.994 0-9.582-1.27-9.757-1.927.005-3.259 4.38-5.91 9.757-5.91 5.376 0 9.75 2.65 9.756 5.909-.17.656-3.758 1.928-9.756 1.928z" /><path d="M8.169 12.236c-3.92.379-6.927 2.459-6.93 4.961.1.383 1.552 1.01 4.08 1.368a6.948 6.948 0 00-.903 1.134C2.029 19.296 0 18.534 0 17.2c0-2.787 2.604-5.09 6.315-5.933a6.1 6.1 0 01-2.87-5.18C3.444 2.73 6.14 0 9.454 0c1.94 0 3.667.936 4.767 2.385-.49.037-.967.125-1.425.258a4.727 4.727 0 00-3.342-1.387c-2.632 0-4.772 2.167-4.772 4.832a4.839 4.839 0 003.031 4.498 7.09 7.09 0 00.455 1.65zM18.595 15.56l-.094.06.128.03a4.757 4.757 0 00-.034-.09z" /></svg>
```

Now, update the navigation to render the icon if the method exists or just show the default icon.

```
  @if(method_exists($resource, 'icon'))
      {!! $resource::icon() !!}
   @else
     <svg class="sidebar-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
                    <path fill="var(--sidebar-icon)" d="M3 1h4c1.1045695 0 2 .8954305 2 2v4c0 1.1045695-.8954305 2-2 2H3c-1.1045695 0-2-.8954305-2-2V3c0-1.1045695.8954305-2 2-2zm0 2v4h4V3H3zm10-2h4c1.1045695 0 2 .8954305 2 2v4c0 1.1045695-.8954305 2-2 2h-4c-1.1045695 0-2-.8954305-2-2V3c0-1.1045695.8954305-2 2-2zm0 2v4h4V3h-4zM3 11h4c1.1045695 0 2 .8954305 2 2v4c0 1.1045695-.8954305 2-2 2H3c-1.1045695 0-2-.8954305-2-2v-4c0-1.1045695.8954305-2 2-2zm0 2v4h4v-4H3zm10-2h4c1.1045695 0 2 .8954305 2 2v4c0 1.1045695-.8954305 2-2 2h-4c-1.1045695 0-2-.8954305-2-2v-4c0-1.1045695.8954305-2 2-2zm0 2v4h4v-4h-4z"/>
      </svg>
  @endif
  <span class="sidebar-label">{{ $resource::label() }}</span>
```
  
  
  
2. Add method to define return the sort value 
To sort the navigation, it can be done at the `navigation.blade.php` but let's dive a little bit deeper into how Nova got those resources lists from. 
if you open `vendor/laravel/nova/src/Tools/ResourceManager.php`, you will find this: 
```
    /**
     * Build the view that renders the navigation links for the tool.
     *
     * @return \Illuminate\View\View
     */
    public function renderNavigation()
    {
        $request = request();
        $groups = Nova::groups($request);
        $navigation = Nova::groupedResourcesForNavigation($request);

        return view('nova::resources.navigation', [
            'navigation' => $navigation,
            'groups' => $groups,
        ]);
    }
```

if you start tracing you ended up on the 

```
    /**
     * Get the sorting strategy to use for Nova resources.
     *
     * @return array
     */
    public static function sortResourcesWith()
    {
        return static::$sortCallback ?? function ($resource) {
            return $resource::label();
        };
    }
```

If you notice, there is a `$sortCallback` function which you can register when Nova boots. So let's do that. 

so in my `NovaServiceProvider.php` in the `boot()` func, we gonna add: 
```
Nova::sortResourcesBy(function ($resource) {
    return method_exists($resource, 'sortOrder') ? 
         $resource::sortOrder() : $resource::label();
});
```

So, we will look for the `sortOrder` method in our resource, if it doesn't it will fall back to the original `label` based sort.

That's all!! 



3. Both navigation and group use the defined sort value 

For the group to order, I just utilize the `sortorder` of resource. 
```
@php
        // dump($navigation);
        $orderNav = $navigation->sortBy(function($resources){
            // dump($resources->first());
            return $resources->first()::sortOrder();
        });
    @endphp
    @foreach($orderNav as $group => $resources)
       ... 
    @endforeach
```




I ended up only showing the icon for the group and here is my `navigation.blade.php`. just FYI: not cleaned up/optimized

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7ldvhfpwtihujscvzcbf.png)
 

```
@if (count(Nova::availableResources(request())))
<ul class="sidemenu">
    @php
        $orderNav = $navigation->sortBy(function($resources){
            return $resources->first()::sortOrder();
        });
    @endphp
    @foreach($orderNav as $group => $resources)
      @if (count($groups) > 1)
      <li class="sidebar-dropdown mb-2">
        <input id="{{$group}}" class="toogle-group" type="checkbox" />
        <label for="{{$group}}" data-toggle="dropdown" class="flex items-center font-normal text-white mb-4 text-base no-underline dim">
            @if(method_exists($resources->first(), 'icon'))
                {!! $resources->first()::icon() !!}
            @endif
            {{ $group }}
            <svg class="text-gray-300 ml-auto h-5 w-5 transform group-hover:text-gray-400 transition-transform ease-in-out duration-150 focus:ring-indigo-500" viewBox="0 0 20 20" aria-hidden="true">
                <path d="M6 6L14 10L6 14V6Z" fill="currentColor" />
              </svg>
        </label>
        
        <ul class="dropdown-menu">
          @foreach($resources as $resource)
          <li>
            <router-link :to="{
                name: 'index',
                params: {
                    resourceName: '{{ $resource::uriKey() }}'
                }
            }" class="flex items-center font-normal text-white mb-4 text-base no-underline dim">
                
                <span class="sidebar-label">{{ $resource::label() }}</span>
            </router-link>
          </li>
          @endforeach
        </ul>
      </li>

      @else
        @foreach($resources as $resource)
        <li class="sidebar-dropdown">
            <router-link :to="{
                name: 'index',
                params: {
                    resourceName: '{{ $resource::uriKey() }}'
                }
            }" class="flex items-center font-normal text-white mb-6 text-base no-underline dim">
                @if(property_exists($resource, 'icon'))
                    {!! $resource::$icon !!}
                @elseif(method_exists($resource, 'icon'))
                    {!! $resource::icon() !!}
                @else
                <svg class="sidebar-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
                    <path fill="var(--sidebar-icon)" d="M3 1h4c1.1045695 0 2 .8954305 2 2v4c0 1.1045695-.8954305 2-2 2H3c-1.1045695 0-2-.8954305-2-2V3c0-1.1045695.8954305-2 2-2zm0 2v4h4V3H3zm10-2h4c1.1045695 0 2 .8954305 2 2v4c0 1.1045695-.8954305 2-2 2h-4c-1.1045695 0-2-.8954305-2-2V3c0-1.1045695.8954305-2 2-2zm0 2v4h4V3h-4zM3 11h4c1.1045695 0 2 .8954305 2 2v4c0 1.1045695-.8954305 2-2 2H3c-1.1045695 0-2-.8954305-2-2v-4c0-1.1045695.8954305-2 2-2zm0 2v4h4v-4H3zm10-2h4c1.1045695 0 2 .8954305 2 2v4c0 1.1045695-.8954305 2-2 2h-4c-1.1045695 0-2-.8954305-2-2v-4c0-1.1045695.8954305-2 2-2zm0 2v4h4v-4h-4z"
                    />
                </svg>
                @endif
                <span class="sidebar-label">{{ $resource::label() }}</span>
            </router-link>
        </li>
        @endforeach
      @endif
    @endforeach
</ul>
@endif
```