I’m having troubles getting my head around the active-state handling of a menu when the menu isn’t used for routing.
I come from Drupal where the menu system handles the routing as well. so setting active state and active-trail state is handled by the route (that acts as a menu rendering system as well).
Now, a lot of PHP frameworks have Router classes that handle the routing. This seems a good separation since a Menu should not be aware of POST || OPTIONS || … requests.
But when writing the frontend, I found myself hard coding the menu. Or storing everything in the DB and passing those values to a view. What I don’t like this approach is that you are kind of creating a copy of what you already wrote in your Router but now using the Menu class.
An example:
Route::get('/somewhere','routename.somewhere','showStuffController');
Route::post('/somewhere','routename.somewhere','saveStuffController');
Menu::add('label.somewhere','routename.somewhere');
You are separating concerns here, so that is nice. But Menu depends heavily in Route to set its active state. The menu will also have to know about hierarchy to set active-trail.
So yes, setting active trail, and active status classes is actually a view thing. But having
if ( Route::currentName() === $menuitem->getRouteName() ) { print 'active'; }
all over your views seem stupid. Then add all those annoying active-trail if’s and it’s a real bloat. Handling that before the view gets renders and setting an active-trail flag to true just seems so ugly the way I do it know (a foreach looping over all children that loop over all children, …)
My question is:
Is there a pattern or a smart way to get this cleaner, better, …? How should one handle the active-trail ‘problem’?
I was thinking of rendering child -> parent. So start with the ad the deepest level and then work my way up. But then the child knows about its parent but the parent knows nothing about his children (seems weird).
when the menu isn’t used for routing
I would say that routing can be used for the menu.
As you already pointed out, the router would be a nice place to hook in. I don’t think that it would be ugly to use a hook which evaluates the menu meta for the current page on each request.
If you make your views responsible for tracking the active state you do not separate concerns. The views should do whatever they are made for – but it is not necessary that they also manage their menu state. The menu meta data is usually application wide the same and you only need the route to be able to locate yourself and render the menu.
Depending on your router and your needs a simple function or class that takes some static menu meta data and the current route would be enough to provide all information you need afterwards.
The menu meta itself must not necessarily be an object. A simple key value data structure without methods should be enough in most cases.
The hook might create a state object with some common functionalities related to your menu, like breadcrumbs, depth, parent page or current page and make this object known in the context of your http request. You have different possibilities inside of the hook – but generally it is about gathering, preparing and passing necessary data to something that knows how to deal with it.
This approach scales with your needs and has a few advantages:
- Your database does not need to deal with data, that you can provide on runtime with low costs
- You’ll have your menu (meta) in one place which makes it maintainable
- If you want your menu to completely rely 1:1 on your routes it can be achieved by providing menu meta dynamically
- If your content grows (and therefore the menu) you can move this data into the session which might be written into a fast key value store