following simple code:
<li>List</li>
is there a simple way to add an class="active" if the current page matches the _list route?
using the newest PR-Release of symfony2 and twig as template engine
Twig allows for conditionals and the Request object is available throughout the application. If you are including the template, to get the route you want to use:
app.request.attributes.get('_route')
If you are using the render function, you want to use:
app.request.attributes.get('_internal')
With that, you should be able to use:
class="{% if app.request.attributes.get('_route') == '_list' %}active{% endif %}"
or shorter:
class="{{ app.request.get('_route') == '_list' ? 'active' }}"
Sometimes you don't want to do exact matching of a route. For those cases, you can use the "starts with" conditional logic of twig.
As an example, lets assume you are working with books. You have the following routes: book, book_show, book_new, book_edit. You want the navigation item Book to be highlighted for any of those cases. This code would accomplish that.
<a class="{% if app.request.attributes.get('_route') starts with 'book' %}active{% endif %}">Books</a>
<a class="{% if app.request.attributes.get('_route') starts with 'author' %}active{% endif %}">Authors</a>
This example works with at least Symfony 2.3.x
Shortest version:
{% set route = app.request.get('_route') %}
<li class="{{ route starts with 'post' ? 'open' }}"></li>
<li class="{{ route starts with 'category' ? 'open' }}"></li>
Sometimes useful:
{% set route = app.request.get('_route') %}
<li class="{{ 'post' in route ? 'open' }}"></li>
<li class="{{ 'category' in route ? 'open' }}"></li>
With ternary operator:
{% set route = app.request.attributes.get('_route') %}
<ul class="nav navbar-nav">
<li {{ route == 'profile_index' ? 'class="active"' }}><i class="icon-profile position-left"></i> My Profile</li>
<li {{ route == 'influencers_index' ? 'class="active"'}}><i class="icon-crown position-left"></i> Influencers</li>
<li {{ route == 'task_manager_index' ? 'class="active"'}}><i class="icon-alarm-check position-left"></i> Task Manager</li>
</ul>
This is done with symfony 3.4, but probably something similar can be done with SF2.
src\AppBundle\Twig\AppExtension.php
<?php
namespace AppBundle\Twig;
use Symfony\Component\HttpFoundation\RequestStack;
class AppExtension extends \Twig_Extension
{
private $requestStack;
public function __construct(RequestStack $requestStack)
{
$this->requestStack = $requestStack;
}
public function getFunctions()
{
return [
new \Twig_SimpleFunction('activeMenu', [$this, 'activeMenu'])
];
}
/**
* Pass route names. If one of route names matches current route, this function returns
* 'active'
* #param array $routesToCheck
* #return string
*/
public function activeMenu(array $routesToCheck)
{
$currentRoute = $this->requestStack->getCurrentRequest()->get('_route');
foreach ($routesToCheck as $routeToCheck) {
if ($routeToCheck == $currentRoute) {
return 'active';
}
}
return '';
}
}
Add this to services.yml
services:
#... some other services
AppBundle\Twig\AppExtension:
arguments: ["#request_stack"]
Usage:
<ul class="nav navbar-nav">
<li class="{{ activeMenu(['form', 'edit_form']) }}">Form</li>
<li class="{{ activeMenu(['list']) }}">List</li>
</ul>
i found a very good Bundle that handles all this stuff automagically:
https://github.com/KnpLabs/KnpMenuBundle
SF2.2
{{ dump(app.request.server.get('PATH_INFO')) }}
Symfony2.3, in Twig, try this to get uri:
{{ dump(app.request.server.get("REQUEST_URI")) }}
I found more efficient to know if we are on the active page and the link or not:
in file xx.html.twig :
In on your header file
{% set route_name = app.request.attributes.get('_route') %}
And add in html class of twig
{% if route_name matches '{^issue}' %}active{% endif %}
This is how I do it (using Symfony 2.6)
<li {% if app.request.get('_route') == '_homepage' %} class="active" {% endif %}>Student</li>
'_homepage' is the name of route in routing.yml of your bundle and the route looks like this
_homepage:
path: /
defaults: { _controller: CoreBundle:Default:index }
class="class_name {% if loop.index0 == 0 %}CLASSNAME{% endif %}"
Related
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?
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.
I have created a custom action that renders a small form at the bottom of my show template for orders. The form is a basic checkbox and a select field to with tow buttons. It works perfectly but the rendering is not right.
I know the way I render the show template is not 100% correct, because when it renders, the left hand side menu doesn't work anymore.
Here is my custom controller with action;
namespace Qi\Bss\FrontendBundle\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Qi\Bss\FrontendBundle\Crud\Crud;
use Qi\Bss\BaseBundle\Entity\Business\PmodOrder;
use Symfony\Component\HttpFoundation\RedirectResponse;
class PmodOrderController extends Controller
{
/**
* #Route("/{id}/approve", name = "order_approve")
* #Security("is_granted('IS_AUTHENTICATED_FULLY')")
* #Method({"GET", "POST"})
*/
public function approveAction(Request $request, $id){
$em = $this->getDoctrine()->getManager();
$order = $em->getRepository('QiBssBaseBundle:PmodOrder')->find($id);
$approveForm = $this->createFormBuilder($order)
->add('requireApproval', 'checkbox', array('label' => 'Require second Approval', 'required' => false, 'mapped' => false))
->add('secondApprover', 'choice', array('choices' => Crud::enumStatus(), 'label' => 'User', 'required' => false))
->getForm();
$approveForm->handleRequest($request);
if ($approveForm->isSubmitted() && $approveForm->isValid()) {
$secondApproval = $request->request->get('form');
$approval = $approveForm->getData();
if (isset($secondApproval['requireApproval'])) {
$approval->setStatus(PmodOrder::STATUS_PARTLY_APPROVED);
$em->persist($approval);
$em->flush();
return new RedirectResponse($this->container->get('router')->generate('admin_bss_base_business_pmodorder_show', array('id' => $order->getId())));
} else {
$approval->setSecondApprover(NULL);
$approval->setStatus(PmodOrder::STATUS_APPROVED);
$em->persist($approval);
$em->flush();
return new RedirectResponse($this->container->get('router')->generate('admin_bss_base_business_pmodorder_show', array('id' => $order->getId())));
}
}
return $this->render('QiBssFrontendBundle:PmodOrder:order_approve.html.twig', array(
'order' => $order,
'form' => $approveForm->createView(),
));
}
}
What bothers me is the fact that I'm actually suppose to extend from Sonata's CRUDController. And when I do that I get an error;
An exception has been thrown during the rendering of a template
("There is no _sonata_admin defined for the controller
Path\To\Controller\PmodOrderController and the current
route ``")
And I am also aware that I'm actually suppose to use a return like return new RedirectResponse($this->admin->generateUrl('show'));
At this point I don't know what to do anymore. If somebody can please guide me how to extend correctly from CRUDController in my scenario, it would be really appreciated
Here an example, I don't know if it's the best solution but I hope that can help you :
1- Create a custom CRUDcontroller :
# CustomCRUDcontroller.php :
class CustomCRUDDController extends Controller
{
/**
* Show action.
*
* #param int|string|null $id
* #param Request $request
*
* #return Response
*
* #throws NotFoundHttpException If the object does not exist
* #throws AccessDeniedException If access is not granted
*/
public function showAction($id = null)
{
$request = $this->getRequest();
// DO YOUR LOGIC IN THE METHOD, for example :
if(isset($request->get('yourFormParam'))){
$this->doTheJob();
}
$id = $request->get($this->admin->getIdParameter());
$object = $this->admin->getObject($id);
if (!$object) {
throw $this->createNotFoundException(sprintf('unable to find the object with id : %s', $id));
}
$this->admin->checkAccess('show', $object);
$preResponse = $this->preShow($request, $object);
if ($preResponse !== null) {
return $preResponse;
}
$this->admin->setSubject($object);
return $this->render($this->admin->getTemplate('show'), array(
'action' => 'show',
'object' => $object,
'elements' => $this->admin->getShow(),
), null);
}
}
2- Register it in admin.yml :
# admin.yml :
x.admin.x:
class: Namespace\YourAdminClass
arguments: [~, Namespace\Entity, Namespace:CustomCRUD]
tags:
- {name: sonata.admin, manager_type: orm, group: X, label: X}
3- Create your own custom_show.html.twig (just a copy and paste of the original template base_show.html.twig located in the sonata-admin folder), here you can display extra elements to the view :
# custom_show.html.twig :
{% extends base_template %}
{% import 'SonataAdminBundle:CRUD:base_show_macro.html.twig' as show_helper %}
{% block actions %}
{% include 'SonataAdminBundle:CRUD:action_buttons.html.twig' %}
{% endblock %}
{% block tab_menu %}
{{ knp_menu_render(admin.sidemenu(action), {
'currentClass' : 'active',
'template': sonata_admin.adminPool.getTemplate('tab_menu_template')
}, 'twig') }}
{% endblock %}
{% block show %}
<div class="sonata-ba-view">
{{ sonata_block_render_event('sonata.admin.show.top', { 'admin': admin, 'object': object }) }}
{% set has_tab = (admin.showtabs|length == 1 and admin.showtabs|keys[0] != 'default') or admin.showtabs|length > 1 %}
{% if has_tab %}
<div class="nav-tabs-custom">
<ul class="nav nav-tabs" role="tablist">
{% for name, show_tab in admin.showtabs %}
<li{% if loop.first %} class="active"{% endif %}>
<a href="#tab_{{ admin.uniqid }}_{{ loop.index }}" data-toggle="tab">
<i class="fa fa-exclamation-circle has-errors hide"></i>
{{ admin.trans(name, {}, show_tab.translation_domain) }}
</a>
</li>
{% endfor %}
</ul>
<div class="tab-content">
{% for code, show_tab in admin.showtabs %}
<div
class="tab-pane fade{% if loop.first %} in active{% endif %}"
id="tab_{{ admin.uniqid }}_{{ loop.index }}"
>
<div class="box-body container-fluid">
<div class="sonata-ba-collapsed-fields">
{% if show_tab.description != false %}
<p>{{ show_tab.description|raw }}</p>
{% endif %}
{{ show_helper.render_groups(admin, object, elements, show_tab.groups, has_tab) }}
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% elseif admin.showtabs is iterable %}
{{ show_helper.render_groups(admin, object, elements, admin.showtabs.default.groups, has_tab) }}
{% endif %}
</div>
{{ sonata_block_render_event('sonata.admin.show.bottom', { 'admin': admin, 'object': object }) }}
{% endblock %}
4- Then indicate to your adminController to display your custom_show template when the current route is "show" (instead of the default template base_show.html.twig) :
# YourEntityAdminController.php :
class YourEntityAdminController extends Controller
{
// allows you to chose your custom showAction template :
public function getTemplate($name){
if ( $name == "show" )
return 'YourBundle:Admin:custom_show.html.twig' ;
return parent::getTemplate($name);
}
}
In Twig partial rendered by separate controller, I want to check if current main route equals to compared route, so I can mark list item as active.
How can I do that? Trying to get current route in BarController like:
$route = $request->get('_route');
returns null.
Uri is also not what I'm looking for, as calling below code in bar's twig:
app.request.uri
returns route similar to: localhost/_fragment?path=path_to_bar_route
Full example
Main Controller:
FooController extends Controller{
public function fooAction(){}
}
fooAction twig:
...some stuff...
{{ render(controller('FooBundle:Bar:bar')) }}
...some stuff...
Bar controller:
BarController extends Controller{
public function barAction(){}
}
barAction twig:
<ul>
<li class="{{ (item1route == currentroute) ? 'active' : ''}}">
Item 1
</li>
<li class="{{ (item2route == currentroute) ? 'active' : ''}}">
Item 2
</li>
<li class="{{ (item3route == currentroute) ? 'active' : ''}}">
Item 3
</li>
</ul>
pabgaran's solution should work. However, the original problem occurs probably because of the request_stack.
http://symfony.com/blog/new-in-symfony-2-4-the-request-stack
Since you are in a subrequest, you should be able to get top-level (master) Request and get _route. Something like this:
public function barAction(Request $request) {
$stack = $this->get('request_stack');
$masterRequest = $stack->getMasterRequest();
$currentRoute = $masterRequest->get('_route');
...
return $this->render('Template', array('current_route' => $currentRoute );
}
Haven't run this but it should work...
I think that the best solution in your case is past the current main route in the render:
{{ render(controller('FooBundle:Bar:bar', {'current_route' : app.request.uri})) }}
Next, return it in the response:
public function barAction(Request $request) {
...
return $this->render('Template', array('current_route' => $request->query->get('current_route'));
}
And in your template compares with the received value.
Otherwise, maybe is better to use a include instead a render, if you don't need extra logic for the partial.
in twig you can send request object from main controller to sub-controller as parameter:
{{ render(controller('FooBundle:Bar:bar', {'request' : app.request})) }}
in sub-controller:
BarController extends Controller{
public function barAction(Request $request){
// here you can use request object as regular
$country = $request->attributes->get('route_country');
}
}
I am new to laravel blade and I want to have an automatic active navigation bar,
so I have this code
<li>{{ HTML::clever_link("index", 'Home' ) }}</li>
<li><a class="glow" href='breeder'>Breeder's Profile</a></li>
<li><a class="glow" href='gallery'>Gallery</a></li>
<li><a class="glow" href='contact'>Contact Us</a></li>
I used the clever link as I research to do what i want, but it remove the link class "glow" now I want to add the glow class to the li with the clever link, I tried this
<li>{{ HTML::clever_link("index", 'Home', class="glow" ) }}</li>
but it just gives me error. Thanks
You can simply add an argument to your HTML Macro: (Obviously I don't know how your macro looks like so this is just an example)
HTML::macro('clever_link', function($link, $label, $class = ''){
return ''.$label.'';
});
Usage:
{{ HTML::clever_link("index", 'Home', 'glow') }}
Or something a bit more flexible:
HTML::macro('clever_link', function($link, $label, $attributes = array()){
return '<a href="'.$link.'" '.HTML::attributes($attributes).'>'.$label.'</a>';
});
Usage:
{{ HTML::clever_link("index", 'Home', array('class' => 'glow')) }}
(The HTML::attributes() method allows you to convert an array into an HTML attributes string)
// for navigation menu highlight
HTML::macro('clever_link', function($route, $text, $icon) {
if( Request::path() == $route ) {
$active = "class = 'active'";
}
else {
$active = '';
}
return "<a href = '{url($route)}' $active> <i class = '{$icon}'></i>{$text}</a>";
});
</pre>
Usage:
Make your menu as:
{{ HTML::clever_link("/", 'Home', 'icon-home-2') }}
{{ HTML::clever_link("/aboutus", 'About Us', 'icon-dollor') }}
in your menu's link
OR use
https://github.com/pyaesone17/active-state
I'm developing a navigation system for Symfony 2. It's working really nicely so far. So far, there is a config file like so:
# The menu name ...
primary:
# An item in the menu ...
Home:
enabled: 1
# Routes where the menu item should be shown as 'active' ...
routes:
- "a_route_name"
# Where the link goes to ... the problem ...
target: "a_route_name"
This layout is working nicely, and the menu works. Apart from in my template, I can only generate links using the target value that correspond to routes within an application; i.e, not an external URL.
The template is as follows for generating the navigation:
{# This is what puts the data for the menu into the page currently ... #}
{% set primary_nav = menu_data('primary') %}
<nav role="navigation" class="primary-nav">
<ul class="clearfix">
{% for key, item in primary_nav if item.enabled is defined and item.enabled %}
{% if item.routes is defined and app.request.attributes.get('_route') in item.routes %}
<li class="active">
{% else %}
<li>
{% endif %}
{% if item.target is defined %}
{{ key }}
{% else %}
{{ key }}
{% endif %}
</li>
{% endfor %}
</ul>
</nav>
Is there a simple way to allow the path() function or, something similar to generate URLs from routes, or just simply use a given URL if it validates as one?
I got as far as trying url(), and looked around the docs but couldn't see anything.
You can create a Twig extension that check if a route exists :
if it exists, the corresponding generated url is returned
else, the url (or other stuff) is returned without any change
In your services.yml, declare your twig extension and inject the router component.
Add the following lines and change namespaces :
fuz_tools.twig.path_or_url_extension:
class: 'Fuz\ToolsBundle\Twig\Extension\PathOrUrlExtension'
arguments: ['#router']
tags:
- { name: twig.extension }
Then create a Twig\Extension directory in your bundle, and create PathOrUrlExtension.php :
<?php
namespace Fuz\ToolsBundle\Twig\Extension;
use Symfony\Bundle\FrameworkBundle\Routing\Router;
class PathOrUrlExtension extends \Twig_Extension
{
private $_router;
public function __construct(Router $router)
{
$this->_router = $router;
}
public function getFunctions()
{
return array(
// will call $this->pathOrUrl if pathOrUrl() function is called from twig
'pathOrUrl' => new \Twig_Function_Method($this, 'pathOrUrl')
);
}
public function pathOrUrl($pathOrUrl)
{
// the route collection returns null on undefined routes
$exists = $this->_router->getRouteCollection()->get($pathOrUrl);
if (null !== $exists)
{
return $this->_router->generate($pathOrUrl);
}
return $pathOrUrl;
}
public function getName()
{
return "pathOrUrl";
}
}
You can now use your new function :
{{ pathOrUrl('fuz_home_test') }}
<br/>
{{ pathOrUrl('http://www.google.com') }}
Will display :