Open vishalmelmatti opened 9 years ago
Hi, it's exactly what I'm trying to do !
I think we should use iterators in KnpMenu, see "Filtering only current items".
I installed KnpMenuBundle and created my menu as a service, then in my default controller, I test this :
$root = $this->get('knp_menu.menu_provider')->get('main');
$menu = $root['my-item'];
$itemMatcher = \Knp\Menu\Matcher\Matcher();
// create the iterator
$iterator = new \Knp\Menu\Iterator\CurrentItemFilterIterator($menu->getIterator(), $itemMatcher);
foreach ($iterator as $item) {
echo $item->getName() . " ";
}
But I have a Symfony error : Attempted to call function "Matcher" from namespace "Knp\Menu\Matcher".
So I add these lines at the beginning of my controller :
use Knp\Menu\Matcher\Matcher;
use Knp\Menu\Iterator\CurrentItemFilterIterator;
But I still have this error. I don't understand...
Anyway! After get items infos according to the current page, we could use php functions prev() and next() to find what we want.
Here is a simple example which works for me. Instead of using a "manual array", we should be using the structure array of our KnpMenu.
// DefaultController.php
/**
* @Route("/my-section/{article}", name="section", defaults={"article" = "index"})
*/
public function showAction(Request $request, $article) {
$tpl = array(
'my-first-article-url' => 'article1',
'my-second-article-url' => 'article2',
'my-third-article-url' => 'article3'
);
// Find the previous and next articles according to current article
foreach ($tpl as $k => $v) {
if ($k == $article) {
$next = current($tpl); // current corresponds to next article (weird)
$prev = prev($tpl);
$prev = prev($tpl);
}
}
$prevLink = (!empty($prev)) ? $this->generateUrl('section', array('article' => $prev)) : "";
$nextLink = (!empty($next)) ? $this->generateUrl('section', array('article' => $next)) : "";
return $this->render('section/' . $tpl[$article] . '.html.twig', array(
'prev' => $prevLink,
'next' => $nextLink,
));
}
In a twig file :
<div class="nav-page-bar">
{% if prev is empty == false %}
<a class="prev" href="{{ prev }}">Previous</a>
{% endif %}
{% if next is empty == false %}
<a class="next" href="{{ next }}">Next</a>
{% endif %}
</div>
With KnpMenu array, we could replace "Previous" and "Next" with items labels.
Please, help us to find a solution !
$itemMatcher = \Knp\Menu\Matcher\Matcher();
You are missing new
here, meaning you are doing a function call (but there is no such function) instead of doing a class instantiation
Ok thanks... Yet, the code is exactly like this in the documentation, it will be good to correct it !
I test this :
$root = $this->get('knp_menu.menu_provider')->get('main');
$menu = $root['tout-savoir'];
$itemMatcher = new \Knp\Menu\Matcher\Matcher();
// create the iterator
$iterator = new \Knp\Menu\Iterator\CurrentItemFilterIterator($menu->getIterator(), $itemMatcher);
foreach ($iterator as $item) {
echo $item->getName() . " ";
}
echo
doesn't show anything.
$iterator
returns an object with empty properties...
object(Knp\Menu\Iterator\CurrentItemFilterIterator)[2142]
private 'matcher' =>
object(Knp\Menu\Matcher\Matcher)[2146]
private 'cache' =>
object(SplObjectStorage)[1935]
private 'storage' =>
array (size=0)
...
private 'voters' =>
array (size=0)
empty
$menu is a Knp\Menu\MenuItem object, here is what it looks like :
object(Knp\Menu\MenuItem)[2153]
protected 'name' => string 'tout-savoir' (length=11)
protected 'label' => string 'S'informer' (length=10)
protected 'linkAttributes' =>
array (size=2)
'title' => string 'Tout savoir sur le mal de dos' (length=29)
'class' => string 'hvr-bubble-bottom' (length=17)
protected 'childrenAttributes' =>
array (size=0)
empty
protected 'labelAttributes' =>
array (size=0)
empty
protected 'uri' => string '/symfony3.0/monmaldedos/web/app_dev.php/tout-savoir/' (length=52)
protected 'attributes' =>
array (size=0)
empty
protected 'extras' =>
array (size=1)
'routes' =>
array (size=1)
0 =>
array (size=2)
...
protected 'display' => boolean true
protected 'displayChildren' => boolean true
protected 'children' =>
array (size=5)
'maux' =>
object(Knp\Menu\MenuItem)[2152]
protected 'name' => string 'maux' (length=4)
protected 'label' => string 'Les maux les plus fréquents' (length=28)
protected 'linkAttributes' =>
array (size=2)
...
So I have all informations that are useful for me, but iterator seems to not work. Thank you stof to help me ;-)
Yet, the code is exactly like this in the documentation, it will be good to correct it !
If you click on the "edit" symbol on the top left corner of the article, you can correct it and submit a PR. Can you please do that?
echo doesn't show anything.
To be more precise, echo isn't executed. This is because you're instantiating a matcher without any voters. This way, there is no logic that votes if a menu item is current or not, so no item is marked as current.
You have to enable at least one voter. Take a look at the KnpMenu\Menu\Matcher\Voter
namespace for the available voters.
Well, you should not create a new matcher but get the one registered as a service. Otherwise your matcher will not have any voter configured, and so won't match any item as current
If you click on the "edit" symbol on the top left corner of the article, you can correct it and submit a PR. Can you please do that?
Ok, no problem.
Well, you should not create a new matcher but get the one registered as a service.
How can I do that ?
Maybe I could use my menu item object $menu
and modify a little bit ma next code (when I search previous and next values in my $tpl
array).
I don't understand what are voters but maybe I don't need finally to use them...
Replace the line with $matcher = $this->get('knp_menu.matcher');
The idea that I had works. But the only inconvenient is that I have to list manually the association url => item name.
public function showAction(Request $request, $article) {
$tpl = array(
'my-first-article-url' => 'article1',
'my-second-article-url' => 'article2',
'my-third-article-url' => 'article3'
);
$root = $this->get('knp_menu.menu_provider')->get('main');
$menu = $root['section'];
// Find the previous and next articles according to current article
foreach ($tpl as $k => $v) {
if ($k == $article) {
$next = current($tpl); // current corresponds to next article (weird)
$prev = prev($tpl);
$prev = prev($tpl);
}
}
$prevLink = (!empty($prev)) ? $this->generateUrl('section', array('article' => $prev)) : "";
$nextLink = (!empty($next)) ? $this->generateUrl('section', array('article' => $next)) : "";
return $this->render('section/' . $tpl[$article] . '.html.twig', array(
'prev' => array('target' => $prevLink, 'label' => $menu->getChildren()[$prev]->getLabel()),
'next' => array('target' => $nextLink, 'label' => $menu->getChildren()[$next]->getLabel()),
));
}
Twig
<div class="nav-page-bar">
{% if prev is empty == false %}
<a class="prev" href="{{ prev.target }}">{{ prev.label }}</a>
{% endif %}
{% if next is empty == false %}
<a class="next" href="{{ next.target }}">{{ next.label }}</a>
{% endif %}
</div>
EDIT : 2016-04-15
Ok, I found the solution with matcher and iterators.
DefaultController.php
/**
* @Route("/my-section/{page}", name="my_section", defaults={"page" = "index"})
*/
public function showAction(Request $request, $page) {
$root = $this->get('knp_menu.menu_provider')->get('main'); // get menu
$menu = $root['my-section'];
// get current menu item (= current page)
$matcher = $this->get('knp_menu.matcher');
$iterator = new \Knp\Menu\Iterator\CurrentItemFilterIterator($menu->getIterator(), $matcher);
// $iterator contains only one item
foreach ($iterator as $item) {
$current = $item->getName();
}
// get all sub-pages of menu item : "my-section"
$itemIterator = new \Knp\Menu\Iterator\RecursiveItemIterator($menu);
$children = new \RecursiveIteratorIterator($itemIterator, \RecursiveIteratorIterator::SELF_FIRST);
// add index page
$items = array(
array(
'name' => 'index',
'label' => $menu->getLabel(),
'route' => $this->generateUrl('my_section_index')
)
);
foreach ($children as $c) {
// routeParameter
$param = $c->getExtras()['routes'][0]['parameters']['page'];
$items[] = array(
'name' => $c->getName(),
'label' => $c->getLabel(),
'route' => $this->generateUrl('my_section', array('page' => $param))
);
}
// Find the previous and next pages according to current page
foreach ($items as $k => $item) {
if ($current == $item['name']) {
# get previous page with key
$prev = ($k-1 >= 0) ? $items[$k-1] : array('label' => "", 'route' => "");
$next = current($items);
# if 2 children only : $prev = $next
# so $next is useless
if (!empty($prev['name']) && $prev['name'] == $next['name'])
$next = array('label' => '', 'route' => '');
break;
}
}
// Display
return $this->render('my-section/' . $current . '.html.twig', array(
'prev' => array('route' => $prev['route'], 'label' => $prev['label']),
'next' => array('route' => $next['route'], 'label' => $next['label']),
));
}
Is it possible to find the routeParameter of a menu item easier than below ?
$c->getExtras()['routes'][0]['parameters']['page']
Twig
<div class="nav-page-bar">
{% if prev.route is empty == false %}
<a class="prev" href="{{ prev.route }}">{{ prev.label }}</a>
{% endif %}
{% if next.route is empty == false %}
<a class="next" href="{{ next.route }}">{{ next.label }}</a>
{% endif %}
</div>
This code shows only children pages of "my-section".
Hope it helps ! ;-)
I edited my last post, few errors and I added index page in the loop.
@eved42 i would like to close this issue if its solved for you. would be great to provide this example in the documentation. maybe you could do a pull request to add a doc/05-cookbook.md file and add this there, starting with explaining what you want to achieve and then what you have here.
Hi,
This is common requirement in blogging that when user is on current page, we need to add previous and next links.
Is there any way to get previous and next menu items with respect to the page opened ? Something like,
knp_menu_render('menu', {'next': 1});