1

I have a module in my Laravel app (it could also be a composer package). This module has a view composer which sets an array to all views containing the routes which should be included in the main navigation of the app.

The composer looks like this:


class ContactNavigationComposer
{
    /**
     * Bind data to the view.
     *
     * @param  \Illuminate\View\View  $view
     * @return void
     */
    public function compose(View $view)
    {
        $view->with('contactNavigation', config('contact.navigation'));
    }
}

Then in the mainNav of the app this variable $contactNavigation becomes iterated to generate the entries:

<ul>
# ...
    <li>
        <a 
            class="{{ (request()->is('navigations/areas')) ? 'active' : '' }}" 
            href="{{ route('areas.index') }}">
                Navigation Areas
        </a>
    </li>
    <li>
        <a 
            class="{{ (request()->is('languages')) ? 'active' : '' }}" 
            href="{{ route('languages.index') }}">
                Languages
        </a>
    </li>
    @foreach($contactNavigation as $text => $url)
        <li>
            <a 
                class="{{ (request()->is($url)) ? 'active' : '' }}" 
                href="{{ $url }}">
                    {{ $text }}
            </a>
        </li>
    @endforeach
</ul>

This works perfectly fine but I was wondering if I can have this behavior in a more dynamic way and let composers of different modules using the same array e.g. $modules which contains navigation entries (and other stuff) of different modules. This way I wouldn't have to add module extensions to the applications views later on. So my suggested solution would be smth. like that:

class ContactNavigationComposer
{
    /**
     * Bind data to the view.
     *
     * @param  \Illuminate\View\View  $view
     * @return void
     */
    public function compose(View $view)
    {
        $view->with('modules.navigation.contact', config('contact.navigation'));
    }
}
<ul>
    # ...
    @if (isset($modules['navigation']))
        @foreach($modules['navigation'] as $moduleNavigation)
            @foreach($moduleNavigation as $text => $url)
                <li>
                    <a 
                        class="{{ (request()->is($url)) ? 'active' : '' }}" 
                        href="{{ $url }}">
                            {{ $text }}
                    </a>
                </li>
            @endforeach
        @endforeach
    @endif
</ul>

Of course with the dot-notation, this modules.navigation.contact-key is never translated into a view variable especially not as an array.

Is there any way to achieve something like that?

Danaq
  • 633
  • 1
  • 6
  • 18

1 Answers1

0

For those who came across this issue as well: I changed my compose method to this

// ViewComposer
class ContactNavigationComposer extends BaseComposer
{

    public function compose(View $view)
    {
        $moduleDataBag = $this->getModuleDataBag($view);

        $moduleData = $moduleDataBag->mergeRecursive([
            'contact' => [
                'navigation' => config('contact.navigation.entries')
            ]
        ]);

        $view->with('modules', $moduleData);
    }
}

The getModuleDataBag-method comes from the BaseComposer-class from which each ViewComposer inherits from.

class BaseComposer
{
    protected function getModuleDataBag(View $view)
    {
        if (!array_key_exists(config('contact.view.dataKey'), $view->gatherData())) {
            ViewFacade::share(config('contact.view.dataKey'), collect([]));
        }

        return $view->gatherData()[config('contact.view.dataKey')];
    }
}

The key in which all the configuration is stored is defined in the (publishable) config. This way this setup is flexible for modifications by the surrounding application. (The package is called contact, that's why the view data key is in the contact-config.) If the expected key is not found, the BaseComposer sets it to all views using the ::share-method on the View-facade as an empty collection.

Each ViewComposer always call the getModuleDataBag-method first to receive the current data probably already set by other ViewComposers.

As this $moduleDataBag is a collection, all additional values can be merged into as long as they are arrays. This is done recursively so that each existing key and value is preserved.

Hope that can help someone later on.

Danaq
  • 633
  • 1
  • 6
  • 18