AdminLTEBundle
AdminLTEBundle copied to clipboard
Breadcrumbs that do not appear in main menu (KNP Menu)
Hi, I am just wonder if there is any existing feature that will allow non left navigation menu item to be displayed in BreadcrumbMenu.
For example, if I have a User Management module.
View in Left Navigation Menu as below: User Management
View in Breadcrumb Menu will be as below: Home -> User
Now my User Management page consist of a list of users. When I click on the specific user, it will redirected to the user page for editing purposes. What I want to achieve is at this specific page to display the Breadcrumb Menu as below:
Home -> User -> Edit
Or at least stay as Home -> User, for now it only display Home.
Your comment or feedback is very much appreciated.
Its up to you what you display in the breadcrumb: https://github.com/kevinpapst/AdminLTEBundle/blob/master/Resources/docs/breadcrumbs.md
You don't have to use the same source for SidebarMenuEvent::class and BreadcrumbMenuEvent::class
Same question here, and with the doc I don't understand either..
In my case it is also with a user CRUD, sidebar display only : list and create, but I want to display on the breadcrumb the show status too.
I've subscribed to the BreadcrumbMenuEvent => 'onSetupNavbar',
but I don't have any navbar, only left menu (sidebar).
And my function onSetupNavbar is never triggered..
Do you have any exemple/demo (with files) on knp mode ?
My code:
<?php
namespace App\EventSubscriber;
use KevinPapst\AdminLTEBundle\Event\BreadcrumbMenuEvent;
use KevinPapst\AdminLTEBundle\Event\KnpMenuEvent;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class KnpMenuBuilderSubscriber implements EventSubscriberInterface
{
/** @var ContainerInterface */
private $container;
function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public static function getSubscribedEvents(): array
{
return [
KnpMenuEvent::class => ['onSetupMenu', 100],
BreadcrumbMenuEvent::class => ['onSetupNavbar', 100],
];
}
public function onSetupNavbar(BreadcrumbMenuEvent $event){
$items = $event->getItems();
$request = $event->getRequest();
$active = $event->getActive();
}
public function onSetupMenu(KnpMenuEvent $event)
{
$menu = $event->getMenu();
/* ========== DASHBOARD ========== */
$menu->addChild('dashboard', [
'route' => 'dashboard',
'label' => 'Dashboard',
'childOptions' => $event->getChildOptions()
])->setLabelAttribute('icon', 'fa fa-columns');
/* ========== CLIENTS ========== */
$client_menu = $menu->addChild('client', [
'label' => 'Clients',
'childOptions' => $event->getChildOptions()
])->setLabelAttribute('icon', 'fa fa-users');
/* --- CLIENTS/Create --- */
$client_menu->addChild('client_new', [
'route' => 'client_new',
'label' => 'Create',
'childOptions' => $event->getChildOptions()
])->setLabelAttribute('icon', 'fa fa-user-plus');
/* --- CLIENTS/List --- */
$client_menu->addChild('client_index', [
'route' => 'client_index',
'label' => 'List',
'childOptions' => $event->getChildOptions()
])->setLabelAttribute('icon', 'fa fa-users');
}
}
My admin_lte.yaml :
admin_lte:
knp_menu:
enable: true
main_menu: adminlte_main
breadcrumb_menu: true
My services.yaml :
[...]
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
[...]
The demo app https://github.com/kevinpapst/AdminLTEBundle-Demo has a KNP mode as well.
Simply switch this to true: https://github.com/kevinpapst/AdminLTEBundle-Demo/blob/master/config/packages/admint_lte.yaml#L41
@kevinpapst Thanks for the fast anwser !! I've upadted my question (a lot, I didn't think you will respond that fast).
I just added my admin_lte.yaml and services.yaml. As you can see, knp_menu is already enabled.
I should have said that before, but the knp_menu and breadcrumb work well together. But if I've a link that not in the sidebar menu (knp_menu), the breadcrumb doesn't work anymore.
Thats because you set it to true which means "re-use the main menu for the breadcrumb, see https://github.com/kevinpapst/AdminLTEBundle/blob/master/Resources/views/Breadcrumb/knp-breadcrumb.html.twig#L5
Switch breadcrumb_menu: true to breadcrumb_menu: my_breadcrumb_menu, see https://github.com/kevinpapst/AdminLTEBundle/blob/master/Resources/docs/knp_menu.md#enabling-breadcrumb-support
Ok I get it for the alias_menu option, but what I don't understand, is why do I need to make a complete menu builder for that case ? I don't want to remake the wheel ..
There no way to 'autoWire' all controllers links to the breadcrumb ? Do I need to defined manually all links that the breadcrumb can pass through ?
Maybe I don't understand your use case. Maybe you just want to overwrite the breadcrumb menu template. Maybe you want to add/use a feature that does not yet exist.
If you want to auto-wire all routes (that allow GET), then create a new MenuBuilder that does that. Or create a new Annotation and to it during compile time, Symfony has so many options.
Please go ahead and sent in a PR if you have a good idea how to make that simpler for everyone!
Yeah I'll try to find a way to do what I want.. Looking for the 'cookie' way for fast dev. Going later on Menu builder if I'm satisfied
There is the case I try to do :
[sidebar] (treeview)
Clients
╚>Create (/client/create)
╚> List (/client)
Others data
╚> ...

[breadcrumb]
- (/client/create) OK

- (/client) OK

- (/client/1) NOT ok nothing is displayed.
I'm on a detailled view of a specific user, url is like : /client/1 I want it to be like 'Clients > Detail' or 'Clients > Show'
Yeah, not sure about that. As I said: never used it like this by myself. So in order to find an answer I would have to debug as well, but as I don't need it (and have no project that uses KNP menu) ... all I can say is: sorry, but I don't think its possible out of the box.
But I would appreciate if you would share your results, might it be documentation or a PR, so it becomes easier for the next one.
Just an idea: only create a clients menu entry, then add a "create" button somewhere on that initial page and write the breadcrumb code manually on each page.
Sadly there is no way to get the Request with KnpMenuEvent..
Trying to work around a voter with KnpMenu/Matcher .
Code looks like that today :
public static function getSubscribedEvents(): array
{
return [
KnpMenuEvent::class => ['onSetupMenu', 100],
];
}
public function onSetupMenu(KnpMenuEvent $event,$eventName, EventDispatcherInterface $dispatcher)
{
$currentURL = $_SERVER['REQUEST_URI'];
$matcherUri = new Matcher(new UriVoter($currentURL));
$rendererUri = new ListRenderer($matcherUri);
$htmlUri = $rendererUri->render($menu);
$matcherRoute = new Matcher(new RouteVoter());
$rendererRoute = new ListRenderer($matcherRoute);
$htmlRoute = $rendererRoute->render($menu);
$items = $menu->getChildren();
$matcher = new Matcher(new RegexVoter($currentURL));
$renderer = new ListRenderer($matcher);
$html = $renderer->render($menu);
}
Doesn't work, even for normal link that should .. I Try to continue tomorrow.
You get the request like this:
public function __construct(RequestStack $requestStack)
{
$this->request = $requestStack->getCurrentRequest();
}
@kevinpapst Did it ....
So, if you have to display a breadcrumb with a link that doesn't require parameters you can add an item into the KnpMenuBuilderSubscriber with a display at false, and it's done.
But if you want to do the same with parameters, like:
@Route("/client/{id}", name="client_show", methods={"GET"})
It is not that simple than the first method.
I had to do a custom voter that looking pretty much like the default RouteVoter from KnpMenu, expect I've removed the 'parameters check' part.
And use that item into my menu :
/* --- CLIENTS/Show --- */
$client_menu->addChild('client_show', [
'route' => 'client_show',
'routeParameters' => ['id' => 0],
'label' => 'Show',
'childOptions' => $event->getChildOptions()
])->setDisplay(false);
Note
You have three way to hide an item menu :
addChildoptions => 'attributes' => ['class' => 'hidden']addChild(...)->setAttribute('class','hidden');addChild(...)->setDisplay(false);setDisplay being my favorite
$client_menu->addChild('test_hidden', [
'route' => 'test_hidden',
'label' => 'Show',
//'attributes' => ['class' => 'hidden'],
'childOptions' => $event->getChildOptions()
])->setDisplay(false);//->setAttribute('class','hidden');
Files
I can't do a PR, but I can give you my files
- [NEW File] RouteNameVoter
<?php
// src/Matcher/Voter/RouteNameVoter.php
namespace App\Matcher\Voter;
use Knp\Menu\ItemInterface;
use Knp\Menu\Matcher\Voter\VoterInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Voter based on the route name only (ignore parameters)
*/
class RouteNameVoter implements VoterInterface
{
/**
* @var RequestStack|null
*/
private $requestStack;
/**
* @var Request|null
*/
private $request;
public function __construct($requestStack = null)
{
if ($requestStack instanceof RequestStack) {
$this->requestStack = $requestStack;
} elseif ($requestStack instanceof Request) {
@trigger_error(sprintf('Passing a Request as the first argument for "%s" constructor is deprecated since version 2.3 and won\'t be possible in 3.0. Pass a RequestStack instead.', __CLASS__), E_USER_DEPRECATED);
// BC layer for the old API of the class
$this->request = $requestStack;
} elseif (null !== $requestStack) {
throw new \InvalidArgumentException('The first argument of %s must be null, a RequestStack or a Request. %s given', __CLASS__, is_object($requestStack) ? get_class($requestStack) : gettype($requestStack));
} else {
@trigger_error(sprintf('Not passing a RequestStack as the first argument for "%s" constructor is deprecated since version 2.3 and won\'t be possible in 3.0.', __CLASS__), E_USER_DEPRECATED);
}
}
public function matchItem(ItemInterface $item)
{
if (null !== $this->requestStack) {
$request = $this->requestStack->getMasterRequest();
} else {
$request = $this->request;
}
if (null === $request) {
return null;
}
$route = $request->attributes->get('_route');
if (null === $route) {
return null;
}
$routes = (array) $item->getExtra('routes', []);
foreach ($routes as $testedRoute) {
if (\is_string($testedRoute)) {
$testedRoute = ['route' => $testedRoute];
}
if (!\is_array($testedRoute)) {
throw new \InvalidArgumentException('Routes extra items must be strings or arrays.');
}
if ($this->isMatchingRouteName($request, $testedRoute)) {
return true;
}
}
return null;
}
private function isMatchingRouteName(Request $request, array $testedRoute)
{
$route = $request->attributes->get('_route');
if (isset($testedRoute['route'])) {
if ($route !== $testedRoute['route']) {
return false;
}
} elseif (!empty($testedRoute['pattern'])) {
if (!\preg_match($testedRoute['pattern'], $route)) {
return false;
}
} else {
throw new \InvalidArgumentException('Routes extra items must have a "route" or "pattern" key.');
}
return true;
}
}
- [Example] KnpMenuBuilderSubscriber
<?php
//src/EventSubscriber/KnpMenuBuilderSubscriber.php
namespace App\EventSubscriber;
use App\Entity\User;
use App\Matcher\Voter\RouteNameVoter;
use KevinPapst\AdminLTEBundle\Event\KnpMenuEvent;
use Knp\Menu\ItemInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Core\Security;
class KnpMenuBuilderSubscriber implements EventSubscriberInterface
{
/* @var Security */
protected $security;
/* @var Request */
protected $request;
public function __construct(RequestStack $requestStack,Security $security)
{
$this->security = $security;
$this->request = $requestStack->getCurrentRequest();
}
public static function getSubscribedEvents(): array
{
return [
KnpMenuEvent::class => ['onSetupMenu', 100],
];
}
public function onSetupMenu(KnpMenuEvent $event)
{
$menu = $event->getMenu();
/* ========== DASHBOARD ========== */
$menu->addChild('dashboard', [
'route' => 'dashboard',
'label' => 'Dashboard',
'childOptions' => $event->getChildOptions()
])->setLabelAttribute('icon', 'fa fa-columns');
if ($this->security->isGranted(User::CST_Role_User)) {
/* ========== CLIENTS ========== */
$client_menu = $menu->addChild('client', [
'label' => 'Clients',
'childOptions' => $event->getChildOptions()
])->setLabelAttribute('icon', 'fa fa-users');
/* --- CLIENTS/Create --- */
$client_menu->addChild('client_new', [
'route' => 'client_new',
'label' => 'Create',
'childOptions' => $event->getChildOptions()
])->setLabelAttribute('icon', 'fa fa-user-plus');
/* --- CLIENTS/List --- */
$client_menu->addChild('client_index', [
'route' => 'client_index',
'label' => 'List',
'childOptions' => $event->getChildOptions()
])->setLabelAttribute('icon', 'fa fa-users');
/* --- CLIENTS/Show --- */
$client_menu->addChild('client_show', [
'route' => 'client_show',
'routeParameters' => ['id' => 0],
'label' => 'Show',
//'attributes' => ['class' => 'hidden'],
'childOptions' => $event->getChildOptions()
])->setDisplay(false);//->setAttribute('class','hidden');
/* ========== CONSTRUCTION SITES ========== */
$construction_menu = $menu->addChild('construction_sites', [
'label' => 'Construction sites',
'childOptions' => $event->getChildOptions()
])->setLabelAttribute('icon', 'fas fa-briefcase');
}
// IMPORTANT PART
$this->activateByRouteName($this->request->attributes->get('_route'),$menu->getChildren());
}
/**
* @param string $routeName
* @param ItemInterface[] $items
*/
protected function activateByRouteName($routeName, $items)
{
$routeNameVoter = new RouteNameVoter($this->request);
foreach ($items as $item) {
if ($item->hasChildren()) {
$this->activateByRouteName($routeName, $item->getChildren());
} else {
// USE the CUSTOM voter
if ($routeNameVoter->matchItem($item) === true) {
$item->setCurrent(true);
break;
}
}
}
}
}
Also, the breadcrumb words with KNP are not using the trans filter from twig template in your knp-breadcrumb.html.twig
<a href="{{ item.uri }}">{{ item.label }}</a>
Must be : <a href="{{ item.uri }}">{{ item.label | trans }}</a>
Thanks for sharing, I leave this issue open in case someone else needs a solution.
As said before: I haven't used the KNP Menu. If you think the translation must happen in the template, please send in a PR. Simply open https://github.com/kevinpapst/AdminLTEBundle/blob/master/Resources/views/Breadcrumb/knp-breadcrumb.html.twig, click edit and GitHub will guide you through the rest.