From c91a667c70e3ee726d2035a2b56d727daf8c58e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Sun, 12 Oct 2014 20:38:26 +0200 Subject: [PATCH] add chill_menu to render easily menu, refs #179 --- Resources/config/services.yml | 15 ++- Resources/views/Menu/defaultMenu.html.twig | 5 + Routing/MenuComposer.php | 18 ++- Routing/MenuTwig.php | 107 ++++++++++++++++++ Tests/Fixtures/App/config/config.yml | 2 +- Tests/Fixtures/App/config/routing.yml | 11 +- .../views/menus/fakeTemplate.html.twig | 1 + .../views/menus/normalMenu.html.twig | 1 + .../views/menus/overrideTemplate.html.twig | 1 + Tests/Services/ChillMenuTwigFunctionTest.php | 86 ++++++++++++++ Tests/Services/MenuComposerTest.php | 7 +- composer.json | 3 + 12 files changed, 247 insertions(+), 10 deletions(-) create mode 100644 Resources/views/Menu/defaultMenu.html.twig create mode 100644 Routing/MenuTwig.php create mode 100644 Tests/Fixtures/Resources/views/menus/fakeTemplate.html.twig create mode 100644 Tests/Fixtures/Resources/views/menus/normalMenu.html.twig create mode 100644 Tests/Fixtures/Resources/views/menus/overrideTemplate.html.twig create mode 100644 Tests/Services/ChillMenuTwigFunctionTest.php diff --git a/Resources/config/services.yml b/Resources/config/services.yml index b568ab657..6a3f521c4 100644 --- a/Resources/config/services.yml +++ b/Resources/config/services.yml @@ -4,8 +4,15 @@ parameters: services: chill.main.menu_composer: class: CL\Chill\MainBundle\Routing\MenuComposer + #must be set in function to avoid circular reference with chill.main.twig.chill_menu + calls: + - [setRoute, ["@router"]] + + chill.main.twig.chill_menu: + class: CL\Chill\MainBundle\Routing\MenuTwig arguments: - - "@router" -# cl_chill_main.example: -# class: %cl_chill_main.example.class% -# arguments: [@service_id, "plain_value", %parameter%] + - "@chill.main.menu_composer" + calls: + - [setContainer, ["@service_container"]] + tags: + - { name: twig.extension } diff --git a/Resources/views/Menu/defaultMenu.html.twig b/Resources/views/Menu/defaultMenu.html.twig new file mode 100644 index 000000000..39e20cd00 --- /dev/null +++ b/Resources/views/Menu/defaultMenu.html.twig @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/Routing/MenuComposer.php b/Routing/MenuComposer.php index 29e998f0e..3d1b51c73 100644 --- a/Routing/MenuComposer.php +++ b/Routing/MenuComposer.php @@ -22,10 +22,16 @@ class MenuComposer */ private $routeCollection; - public function __construct(RouterInterface $router) + /** + * + * @internal must be set in function instead of controller to avoid circular reference + * with MenuTwig + * @param RouterInterface $router + */ + public function setRoute(RouterInterface $router) { //see remark in MenuComposer::setRouteCollection - $this->setRouteCollection($router->getRouteCollection()); + $this->routeCollection = $router->getRouteCollection(); } /** @@ -41,6 +47,14 @@ class MenuComposer $this->routeCollection = $routeCollection; } + /** + * Return an array of routes added to $menuId, + * The array is aimed to build route with MenuTwig + * + * @param string $menuId + * @param array $parameters see https://redmine.champs-libres.coop/issues/179 + * @return array + */ public function getRoutesFor($menuId, array $parameters = array()) { $routes = array(); diff --git a/Routing/MenuTwig.php b/Routing/MenuTwig.php new file mode 100644 index 000000000..1ed8db231 --- /dev/null +++ b/Routing/MenuTwig.php @@ -0,0 +1,107 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace CL\Chill\MainBundle\Routing; + +use CL\Chill\MainBundle\Routing\MenuComposer; +use Symfony\Component\DependencyInjection\ContainerAwareInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Add the filter 'chill_menu' + * + * @author Julien Fastré + */ +class MenuTwig extends \Twig_Extension implements ContainerAwareInterface +{ + + /** + * + * @var MenuComposer + */ + private $menuComposer; + + /** + * + * @var \Symfony\Component\DependencyInjection\ContainerInterface + */ + private $container; + + /** + * the default parameters for chillMenu + * + * @var mixed[] + */ + private $defaultParams = array( + 'layout' => 'CLChillMainBundle:Menu:defaultMenu.html.twig', + 'args' => array(), + 'activeRouteKey' => null + ); + + public function __construct(MenuComposer $menuComposer) + { + $this->menuComposer = $menuComposer; + } + + public function getFunctions() + { + return [new \Twig_SimpleFunction('chill_menu', + array($this, 'chillMenu'), array('is_safe' => array('html'))) + ]; + } + + /** + * Render a Menu corresponding to $menuId + * + * Expected params : + * - args: the arguments to build the path (i.e: if pattern is /something/{bar}, args must contain {'bar': 'foo'} + * - layout: the layout. Absolute path needed (i.e.: CLChillXyzBundle:section:foo.html.twig) + * - activeRouteKey : the key active, will render the menu differently. + * + * see https://redmine.champs-libres.coop/issues/179 for more informations + * + * @param string $menuId + * @param mixed[] $params + */ + public function chillMenu($menuId, array $params = array()) + { + $resolvedParams = array_merge($this->defaultParams, $params); + + $layout = $resolvedParams['layout']; + unset($resolvedParams['layout']); + + $resolvedParams['routes'] = $this->menuComposer->getRoutesFor($menuId); + + return $this->container->get('templating') + ->render($layout, $resolvedParams); + } + + public function getName() + { + return 'chill_menu'; + } + + public function setContainer(ContainerInterface $container = null) + { + $this->container = $container; + } + +} diff --git a/Tests/Fixtures/App/config/config.yml b/Tests/Fixtures/App/config/config.yml index 9522a26e4..0046eed72 100644 --- a/Tests/Fixtures/App/config/config.yml +++ b/Tests/Fixtures/App/config/config.yml @@ -8,4 +8,4 @@ framework: translator: { fallback: fr } profiler: { only_exceptions: false } templating: - engines: ['twig'] \ No newline at end of file + engines: ['twig'] diff --git a/Tests/Fixtures/App/config/routing.yml b/Tests/Fixtures/App/config/routing.yml index 9e42a3faf..62747f142 100644 --- a/Tests/Fixtures/App/config/routing.yml +++ b/Tests/Fixtures/App/config/routing.yml @@ -22,4 +22,13 @@ chill_main_dummy_1: dummy0: order: 50 label: 'test1' - helper: 'great helper' \ No newline at end of file + helper: 'great helper' + +chill_main_dummy_2: + pattern: /dummy2/{param} + defaults: {_controller: CLChillMainBundle:Default:index } + options: + menus: + dummy0: + order: 50 + label: test2 \ No newline at end of file diff --git a/Tests/Fixtures/Resources/views/menus/fakeTemplate.html.twig b/Tests/Fixtures/Resources/views/menus/fakeTemplate.html.twig new file mode 100644 index 000000000..3b1236389 --- /dev/null +++ b/Tests/Fixtures/Resources/views/menus/fakeTemplate.html.twig @@ -0,0 +1 @@ +fake template \ No newline at end of file diff --git a/Tests/Fixtures/Resources/views/menus/normalMenu.html.twig b/Tests/Fixtures/Resources/views/menus/normalMenu.html.twig new file mode 100644 index 000000000..af558b6f2 --- /dev/null +++ b/Tests/Fixtures/Resources/views/menus/normalMenu.html.twig @@ -0,0 +1 @@ +{{ chill_menu('dummy0', {'args' : { 'param' :'fake' }, 'activeRouteKey': 'chill_main_dummy_0' }) }} \ No newline at end of file diff --git a/Tests/Fixtures/Resources/views/menus/overrideTemplate.html.twig b/Tests/Fixtures/Resources/views/menus/overrideTemplate.html.twig new file mode 100644 index 000000000..4cf71b30f --- /dev/null +++ b/Tests/Fixtures/Resources/views/menus/overrideTemplate.html.twig @@ -0,0 +1 @@ +{{ chill_menu('dummy1', {'layout' : '@tests/menus/fakeTemplate.html.twig' }) }} \ No newline at end of file diff --git a/Tests/Services/ChillMenuTwigFunctionTest.php b/Tests/Services/ChillMenuTwigFunctionTest.php new file mode 100644 index 000000000..b2afd6d82 --- /dev/null +++ b/Tests/Services/ChillMenuTwigFunctionTest.php @@ -0,0 +1,86 @@ + + * Copyright (C) 2014, Champs Libres Cooperative SCRLFS, + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace CL\Chill\MainBundle\Tests\Services; + +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Symfony\Component\DomCrawler\Crawler; + +/** + * Test the Twig function 'chill_menu' + * + * @author Julien Fastré + */ +class ChillMenuTwigFunctionTest extends KernelTestCase +{ + + private static $templating; + + public static function setUpBeforeClass() + { + self::bootKernel(array('environment' => 'test')); + static::$templating = static::$kernel + ->getContainer()->get('templating'); + //load templates in Tests/Resources/views + static::$kernel->getContainer()->get('twig.loader') + ->addPath(__DIR__.'/../Fixtures/Resources/views/', $namespace = 'tests'); + } + + public function testNormalMenu() + { + $content = static::$templating->render('@tests/menus/normalMenu.html.twig'); + $crawler = new Crawler($content); + + $ul = $crawler->filter('ul')->getNode(0); + $this->assertEquals( 'ul', $ul->tagName); + + $lis = $crawler->filter('ul')->children(); + $this->assertEquals(3, count($lis)); + + $lis->each(function(Crawler $node, $i) { + $this->assertEquals('li', $node->getNode(0)->tagName); + + $a = $node->children()->getNode(0); + $this->assertEquals('a', $a->tagName); + switch($i) { + case 0: + $this->assertEquals('/dummy?param=fake', $a->getAttribute('href')); + $this->assertEquals('active', $a->getAttribute('class')); + $this->assertEquals('test0', $a->nodeValue); + break; + case 1: + $this->assertEquals('/dummy1?param=fake', $a->getAttribute('href')); + $this->assertEmpty($a->getAttribute('class')); + $this->assertEquals('test1', $a->nodeValue); + break; + case 3: + $this->assertEquals('/dummy2/fake', $a->getAttribute('href')); + $this->assertEmpty($a->getAttribute('class')); + $this->assertEquals('test2', $a->nodeValue); + } + }); + } + + public function testMenuOverrideTemplate() + { + $content = static::$templating->render('@tests/menus/overrideTemplate.html.twig'); + $this->assertEquals('fake template', $content); + } +} diff --git a/Tests/Services/MenuComposerTest.php b/Tests/Services/MenuComposerTest.php index 8f6c28755..671f6b146 100644 --- a/Tests/Services/MenuComposerTest.php +++ b/Tests/Services/MenuComposerTest.php @@ -3,7 +3,6 @@ namespace CL\Chill\MainBundle\Tests\Services; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; -use Symfony\Component\Routing\Loader\YamlFileLoader; use Symfony\Component\Routing\RouteCollection; /** @@ -43,7 +42,7 @@ class MenuComposerTest extends KernelTestCase $routes = $this->menuComposer->getRoutesFor('dummy0'); $this->assertInternalType('array', $routes); - $this->assertCount(2, $routes); + $this->assertCount(3, $routes); //check that the keys are sorted $orders = array_keys($routes); foreach ($orders as $key => $order){ @@ -65,6 +64,10 @@ class MenuComposerTest extends KernelTestCase 'key' => 'chill_main_dummy_1', 'label' => 'test1', 'helper'=> 'great helper' + ), + 52 => array( + 'key' => 'chill_main_dummy_2', + 'label' => 'test2' )); diff --git a/composer.json b/composer.json index a5cf513c1..cb68260ee 100644 --- a/composer.json +++ b/composer.json @@ -22,5 +22,8 @@ "symfony/framework-bundle": "2.5.*", "symfony/yaml": "2.5.*", "symfony/symfony": "2.5.*" + }, + "require-dev": { + "symfony/dom-crawler": "2.5" } }