How to check for parameter not defined/not set in twig route? - symfony

I am working with symfony5/twig and I want to highlight a menu item when it's active (the current route).
I'm checking for route parameter as below sample shows, but I have not found a way to deal with the case when no parameter is passed (it is optional).
The below code works fine as long as a route with parameter is used, but once the route with no parameter is used, an error is thrown when it tries to check the app.request.attributes.get('type').id since obviously the app.request.attributes.get('type') is null and hence has no property 'id'.
<li>
<a class="{% if app.request.get('_route') == 'app_support_case_new' and app.request.query.get('type') is not defined %}active{% endif %}" href="{{ path('app_support_case_new') }}">
<span class="glyphicon glyphicon-plus glyphicon-plus" aria-hidden="true"></span> No topic
</a>
</li>
<li>
<a class="{% if app.request.get('_route') == 'app_support_case_new' and app.request.attributes.get('type').id == 1 %}active{% endif %}" href="{{ path('app_support_case_new', {'type':1}) }}">
<span class="glyphicon glyphicon-plus glyphicon-user" aria-hidden="true"></span> Staff <i class="glyphicon glyphicon-random opacity50"></i>
</a>
</li>
<li>
<a class="{% if app.request.get('_route') == 'app_support_case_new' and app.request.attributes.get('type').id == 2 %}active{% endif %}" href="{{ path('app_support_case_new', {'type':2}) }}">
<span class="glyphicon glyphicon-shopping-cart" aria-hidden="true"></span> Order
</a>
</li>
In php I would put an isset(app.request.attributes.get('type')) before it in the conditions, and it would stop checking there. But apparently, twig checks all the conditions regardless.
I've tried adding this:
app.request.attributes.get('type') is defined and
such as
<li>
<a class="{% if app.request.get('_route') == 'app_support_case_new' and app.request.attributes.get('type') is defined and app.request.attributes.get('type').id == 1 %}active{% endif %}" href="{{ path('app_support_case_new', {'type':1}) }}">
<span class="glyphicon glyphicon-plus glyphicon-user" aria-hidden="true"></span> Staff <i class="glyphicon glyphicon-random opacity50"></i>
</a>
</li>
But it doesn't help. The error is still:
Impossible to access an attribute ("id") on a null variable.
<a class="{% if app.request.get('_route') == 'app_support_case_new' and app.request.attributes.get('type') is defined and app.request.attributes.get('type').id == 1 %}active{%endif %}" href="{{ path('app_support_case_new', {'type':1}) }}">

For this use-case you can simply use the filter default
{% if app.request.get('_route') == 'app_support_case_new' and app.request.attributes.get('type')|default == 1 %}active{% endif %}
FYI: The test defined doesn't take in account nullable values and behaves different than doing isset in PHP. That is because the test defined uses array_key_exists in the background. See snippet below
// line 2
echo ((array_key_exists("foo", $context)) ? (1) : (0));
{% set foo = null %}
{{ foo is defined ? 1 : 0 }} {# 1 #}
{{ foo ? 1 : 0 }} {# 0 #}
{% set foo = false %}
{{ foo is defined ? 1 : 0 }} {# 1 #}
{{ foo ? 1 : 0 }} {# 0 #}
{% set foo = 0 %}
{{ foo is defined ? 1 : 0 }} {# 1 #}
{{ foo ? 1 : 0 }} {# 0 #}
demo
<?php
$foo = null;
echo (isset($foo) ? 1 : 0).PHP_EOL;
echo ($foo ? 1 : 0).PHP_EOL;
$foo = false;
echo (isset($foo) ? 1 : 0).PHP_EOL;
echo ($foo ? 1 : 0).PHP_EOL;
$foo = 0;
echo (isset($foo) ? 1 : 0).PHP_EOL;
echo ($foo ? 1 : 0).PHP_EOL;
0
0
1
0
1
0
demo

Related

Find on which item route from the menu I am

I'm currently working on a Symfony 4.4 web application.
The menu can be changed by the administrators of my app, so there is a table with these properties :
Because I got routes with some parameters (eg: a slug or an id, and the slug can change so it's not a great identifier).
The problem is that I want to know on which menu item I am, to keep the menu opened (to add an active class on < li > items) but I can't find a way to do it properly.
What I tried :
Identify the route with something like that :
<li>{% if app.request.get('_route') == 'foo_products_overview' and app.request.get('slug') in ["entityslug"] %} class="active" {% endif %}></li>
But parameters are not the same for each route of my app (mutliple entites using id or slug to find one).
Here is the way my menu items are displayed :
{% if child2.getMenuItems()|length > 0 %}
<ul class="collapse nav-sub" aria-expanded="false">
{% for child3 in child2.getMenuItems() if child2.getMenuItems()|length > 0 and child3.level == 4 and (is_granted(child3.roles) or child3.roles is empty) %}
<li class="{% if child3.getMenuItems()|length > 0 %}nav-dropdown{% endif %}">
<a class="{% if child3.getMenuItems()|length > 0 %}has-arrow{% endif %}"
href="{% if child3.route is not null %}{% if child3.routeParameters %}{{ path(child3.route, {'id': child3.routeParameters}) }}{% else %}{{ path(child3.route) }}{% endif %}{% else %}#{% endif %}"
aria-expanded="false">
<span>{% if "ROLE_ADMIN" in child3.roles %}<i class="la la-eye-slash text-danger mr-2"></i>{% endif %}{{ child3.name|raw }}</span>
</a>
</li>
{% endfor %}
</ul>
{% endif %}
Have you ever had this problem?
Maybe there's a way with KnpMenu? (https://symfony.com/bundles/KnpMenuBundle/current/index.html) I'm not using it for the moment.
Did you try this : replace "child.id" by your variable name, i think that's "child3.id" but not sure.
class="{% if app.request.attributes.get( '_route' ) starts with 'foo_products_overview' and app.request.attributes.get('id') == child.id %}active{% endif%}"

How to get current page in pages Symfony 5 without duplicate code?

I want get the current page and give an active class without duplicate code in symfony 5.
Here is the code example :
<li class="{{ app.request.get('_route_')=='home' ? 'active':''}}">
Home
</li>
<li class="{{ app.request.get('_route_')=='contact' ? 'active':''}}">
Contact
</li>
</ul>
As an extension of the answer of #DhiaDjobbi
I've you really want to reduce "duplicate" code, I'd go with defining the menu in an array as well
{% set active_page = app.request.get('_route') %}
{% for page in pages %}
<li {% if page == active_page %} class="active" {% endif %} >
{{ page }}
</li>
{% endfor %}
U can create a twig variable in Template its better readable.
{% set page = app.request.get('_route') %}
Then use If Condition to test.
<li {% if page == 'home' %} class="active" {% endif %} >
Home
</li>
<li {% if page == 'contact' %} class="active" {% endif %} >
Contact
</li>

how can i append an anchor to the url using knp_paginator

I am using symfony 4.4 to develop an e-commerce website. I have a section with an id="projects" at the centre of the page so you have to scroll down to reach it. This section is built with knp paginator inside my ProductRepository to make it work with the search form, and some parts of the section are links to another page that also has a products section that you have to scroll to. I want to attach #projects to the URLs to scroll the user down to the section, but I don't know how to do this the way that knp paginator builds the page.
I have tried to append a #projects to the path inside twitter_bootstrap_v4_pagination.html.twig (the template that I have defined in the knp_paginator.yaml), but when I do this the paginator doesn't work anymore and #projects is not appended to the url.
{% if pageCount > 1 %}
<nav>
{% set classAlign = (align is not defined) ? '' : align=='center' ? ' justify-content-center' : (align=='right' ? ' justify-content-end' : '') %}
{% set classSize = (size is not defined) ? '' : size=='large' ? ' pagination-lg' : (size=='small' ? ' pagination-sm' : '') %}
<ul class="pagination{{ classAlign }}{{ classSize }}">
{% if previous is defined %}
<li class="page-item">
<a class="page-link" rel="prev" href="{{ path(route, query|merge({(pageParameterName): previous})) }}#projects">« {{ 'label_previous'|trans({}, 'KnpPaginatorBundle') }}</a>
</li>
{% else %}
<li class="page-item disabled">
<span class="page-link">« {{ 'label_previous'|trans({}, 'KnpPaginatorBundle') }}</span>
</li>
{% endif %}
{% if startPage > 1 %}
<li class="page-item">
<a class="page-link" href="{{ path(route, query|merge({(pageParameterName): 1})) }}#projects">1</a>
</li>
{% if startPage == 3 %}
<li class="page-item">
<a class="page-link" href="{{ path(route, query|merge({(pageParameterName): 2})) }}#projects">2</a>
</li>
{% elseif startPage != 2 %}
<li class="page-item disabled">
<span class="page-link">…</span>
</li>
{% endif %}
{% endif %}
{% for page in pagesInRange %}
{% if page != current %}
<li class="page-item">
<a class="page-link" href="{{ path(route, query|merge({(pageParameterName): page})) }}#projects">{{ page }}</a>
</li>
{% else %}
<li class="page-item active">
<span class="page-link">{{ page }}</span>
</li>
{% endif %}
{% endfor %}
{% if pageCount > endPage %}
{% if pageCount > (endPage + 1) %}
{% if pageCount > (endPage + 2) %}
<li class="page-item disabled">
<span class="page-link">…</span>
</li>
{% else %}
<li class="page-item">
<a class="page-link" href="{{ path(route, query|merge({(pageParameterName): (pageCount - 1)})) }}#projects">{{ pageCount -1 }}</a>
</li>
{% endif %}
{% endif %}
<li class="page-item">
</li>
{% endif %}
{% if next is defined %}
<li class="page-item">
<a class="page-link" rel="next" href="{{ path(route, query|merge({(pageParameterName): next})) }}#projects">{{ 'label_next'|trans({}, 'KnpPaginatorBundle') }} »</a>
</li>
{% else %}
<li class="page-item disabled">
<span class="page-link">{{ 'label_next'|trans({}, 'KnpPaginatorBundle') }} »</span>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
If I change #projects to another id that doesn't exist inside my index.html.twig, for example #randomId, the paginator works fine and the url is changed "http://127.0.0.1:8000/home?page=2#randomId", but it doesn't solve my problem.
Note: I have also tried to change manually the url to http://127.0.0.1:8000/home?page=2#projects and it worked. I don't know why it wouldn't accept and existing id inside the index.html.twig.
If needed here is the controller :
<?php
namespace App\Controller;
use App\Data\SearchData;
use App\Entity\Contact;
use App\Entity\ContactDevis;
use App\Entity\Evv;
use App\Form\ContactType;
use App\Form\ContactDevisType;
use App\Form\SearchForm;
use App\Repository\ProduitRepository;
use App\Repository\EvvRepository;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
/**
* #Route("/")
*/
class EnoveController extends AbstractController
{
/**
* #Route("/home", name="enove_index", methods={"GET","POST"})
*/
public function index(ProduitRepository $produitRepository, EvvRepository $evvRepository, Request $request , Request $request_contact, Request $request_devis, \Swift_Mailer $mailer)
{
$contact = new Contact();
$contactdevis = new ContactDevis();
$data = new SearchData();
$evv = new Evv();
$data->page = $request->get('page',1);
$form_filter = $this->createForm(SearchForm::class, $data);
$form_filter->handleRequest($request);
//dd($data);
$evv = $evvRepository->findAll();
$produit = $produitRepository->findSearch($data);
$form_contact = $this->createForm(ContactType::class, $contact);
$form_contact->handleRequest($request_contact);
if($form_contact->isSubmitted() && $form_contact->isValid())
{
$contact = $form_contact->getData();
$this->addFlash('success', 'votre email à été acheminer à Enove');
$message = (new \Swift_Message('Nouveau contact'))
// On attribue l'expéditeur
->setFrom($contact->getEmail())
// On attribue le destinataire
->setTo('zwayten111#gmail.com')
// On crée le texte avec la vue
->setBody(
$this->renderView(
'emails/contact.html.twig', compact('contact')
),
'text/html'
)
;
$mailer->send($message);
return $this->redirectToRoute('enove_index' ,
[
'produits' => $produit,
'form_filter' => $form_filter->createView(),
'evvs' =>$evv,
] );
}
$form_devis = $this->createForm(ContactDevisType::class, $contactdevis);
$form_devis->handleRequest($request_devis);
if($form_devis->isSubmitted() && $form_devis->isValid())
{
$contactdevis = $form_devis->getData();
$this->addFlash('success', 'votre email à été acheminer à Enove');
$message2 = (new \Swift_Message('Devis'))
// On attribue l'expéditeur
->setFrom($contactdevis->getEmail())
// On attribue le destinataire
->setTo('zwayten111#gmail.com')
// On crée le texte avec la vue
->setBody(
$this->renderView(
'emails/contact_devis.html.twig', compact('contactdevis')
),
'text/html'
)
;
$mailer->send($message2);
//dd($contactdevis);
return $this->redirectToRoute('enove_index' ,
[
'produits' => $produit,
'form_filter' => $form_filter->createView(),
'evvs' =>$evv,
] );
}
//dump($request->request);
return $this->render(
'FrontEndEnove/index.html.twig',
[
'produits' => $produit,
'form_filter' => $form_filter->createView(),
'form_contact' => $form_contact->createView(),
'form_devis' => $form_devis->createView(),
'evvs' => $evv,
]
);
}
}
Well in my case I changed the ProductRpository with adding this
$query = $query->getQuery();
$pagination = $this->paginator->paginate($query,$search->page,6);
$pagination->setParam('_fragment', 'projects');
return $pagination;
and the problem was still here, guess what I had a problem in template, the jQuery was forcing it to go back to the top I just had to delete it.

Django CMS Multi-Level Dropdown Menu

Im kinda new to Django CMS and im trying my best to avoid asking, but this one drives me crazy.
I made a Wiki app with a Topic, and Category model. I hooked it to a Site on my CMS and added it to my Menu. Now i would like to be able to show all Top-Level categories, their Child Categories & Topics, and the Child categories of these, and so on, on my menu.
Menu/Navigation should look like this:
Wiki
Category1
Category1.1
Topic
Category1.2
Topic
Category2
Topic
Category3
...
Right now i can only show the Top categories:
Wiki
Category1
Category2
Category3
I Already created a menu.py to get a Custom SubMenu on my Wiki (the one you see above):
menu.py
class WikiSubMenu(CMSAttachMenu):
name = _("Wiki Sub-Menu")
def get_nodes(self, request):
nodes = []
categories = Category.objects.filter(parent_id__isnull=True)
for c in categories:
node = NavigationNode(
mark_safe(c.name),
c.get_absolute_url(),
c.id,
)
nodes.append(node)
return nodes
menu_pool.register_menu(WikiSubMenu)
My Category Model:
class Category(models.Model):
''' Category model. '''
name = models.CharField(max_length=100)
slug = models.SlugField(unique=True)
description = models.TextField(blank=True)
parent = models.ForeignKey(
'self',
null=True,
blank=True,
related_name='children'
)
sort = models.IntegerField(default=0)
class Meta:
ordering = ['sort', 'name']
def __unicode__(self):
return self.name
#models.permalink
def get_absolute_url(self):
return ('topics:categories_category_detail', (), {'slug': self.slug})
def get_all_children(self):
return Category.objects.filter(parent=self)
Now, is it possible to create a Sub-SubMenu, for all Categories with Childs, and their Childs, and their Childs, and so on?
Thanks for help & sorry for bad english
-- EDIT : --
I just found that:
docs.django-cms.org/en/3.0.6/extending_cms/app_integration.html#integration-modifiers
(Removed direct link to add 2 new Links, sorry for that)
I think that is what im looking for, i was kinda blind that i didn't found it. I'll try it out and Post the Answer if it worked out.
-- EDIT (AGAIN): --
The modifier didn't worked for me, but i got a whole piece further,
i read the Docs again, and found that i can give the NavigationNodes an optional attr dictonary, which i filled with all Categories with parent=c, on that way i had the data i needed, then i found that real nice bootstrap dropdown menu, that does exacly what i wanted. So my code until now looks like that:
menu.py
class TopicsSubMenu(CMSAttachMenu):
name = _("Wiki Sub-Menu")
def get_nodes(self, request):
nodes = []
categories = Category.objects.filter(parent_id__isnull=True)
for c in categories:
node = NavigationNode(
mark_safe(c.name),
c.get_absolute_url(),
c.pk,
attr=dict(
subcategories=Category.objects.filter(parent=c),),
)
nodes.append(node)
return nodes
And my Template:
menu.html
{% for child in children %}
<li>
{% if child.children %}
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
{{ child.get_menu_title }}
<span class="caret">
</span>
</a>
<ul class="dropdown-menu multi-level" role="menu" aria-labelledby="dropdownMenu">
{% for child in child.children %}
{% if child.attr.subcategories.count %}
<li class="dropdown-submenu">
<a tabindex="-1" href="#">{{ child.get_menu_title }}</a>
<ul class="dropdown-menu">
{% for subcategory in child.attr.subcategories %}
<li>
<a tabindex="-1" href="{{ subcategory.get_absolute_url }}">{{ subcategory }}</a>
</li>
{% endfor %}
</ul>
</li>
{% else %}
<li>{{ child.get_menu_title }}</li>
{% endif %}
{% endfor %}
</ul>
{% else %}
<a href="{{ child.get_absolute_url }}">
<span>
{{ child.get_menu_title }}
</span>
</a>
{% endif %}
</li>
{% if class and forloop.last and not forloop.parentloop %}
{% endif %}
{% endfor %}
My next step will be to write the whole "for" loops from the template in a Method, make it recursive with a while loop or something and post the result as Answer.
I hope i can help someone with that stuff :)
WOHO! I finally did it!
base.html
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
{% show_menu 0 100 100 100 "menu.html" %}
</ul>
</div>
menu.html:
{% for child in children %}
<li class="child{% if child.selected %} selected{% endif %}{% if child.ancestor %} ancestor{% endif %}{% if child.sibling %} sibling{% endif %}{% if child.descendant %} descendant{% endif %}{% if child.children %} dropdown{% endif %}">
<a {% if child.children %}class="dropdown-toggle" data-toggle="dropdown"{% endif %} href="{{ child.attr.redirect_url|default:child.get_absolute_url }}">
<span>{{ child.get_menu_title }}</span>{% if child.children|length %}<span class="caret"></span>{% endif %}
</a>
{% if child.children %}
<ul class="dropdown-menu multi-level" role="menu" aria-labelledby="dropdownMenu">
{% show_menu from_level to_level extra_inactive extra_active "dropdownmenu.html" "" "" child %}
</ul>
{% endif %}
</li>
{% if class and forloop.last and not forloop.parentloop %}{% endif %}
{% endfor %}
and my dropdownmenu.html:
(The recursive stuff starts here)
{% load i18n menu_tags cache mptt_tags %}
{% for child in children %}
<li {% if child.children %}class="dropdown-submenu"{% else %} {% endif %}>
<a tabindex="-1" href="{{ child.attr.redirect_url|default:child.get_absolute_url }}">{{ child.get_menu_title }}</a>
{% if child.children %}
<ul class="dropdown-menu">
{% show_menu from_level to_level extra_inactive extra_active "dropdownmenu.html" "" "" child %}
</ul>
{% endif %}
</li>
{% endfor %}
and the most important, menu.py:
class TopicsSubMenu(CMSAttachMenu):
name = _("Wiki Sub-Menu")
def get_nodes(self, request):
nodes = []
categories = Category.objects.all()
for c in categories:
node = NavigationNode(
mark_safe(c.name),
c.get_absolute_url(),
c.pk
)
if c.parent:
node.parent_id = c.parent_id
nodes.append(node)
topics = Topic.objects.all().exclude(category__isnull=True)
for t in topics:
node = NavigationNode(
mark_safe(t.title),
t.get_absolute_url(),
t.pk,
t.category.all()[0].id,
parent_namespace="TopicsSubMenu"
)
nodes.append(node)
return nodes
menu_pool.register_menu(TopicsSubMenu)
And thats it!

How to make a macro in Twig?

I use a big piece of code for displaying friends and I use it in several templates, so I wanted to make it a macro, but the syntax is a way unusal and I don't know if there is a way this to be done.
The part of the code, which I want to seperate is:
{% if(fr.email != null) %}
<p>
<span class ="label">Email Address: </span>
<a class="email" href="{{ path('friend_email', {'id': fr.id}) }}">
{{ fr.email }}
</a>
</p>
{% endif %}
{% if(fr.phone != null) %}
<p>
<span class="label">Phone: </span>
{{ fr.phone }}
</p>
{% endif %}
and so on for about 10 variables. In another template I use this, but instead of fr.email, fr.phone and so on, I need friend.email, friend.phone...
I tried this but without success:
{% macro display_friend(fr) %}
{% if({{ fr }}.email != null) %}
<p>
<span class ="label">Email Address: </span>
<a class="email" href="{{ path('friend_email', {'id': {{ fr }}.id}) }}">
{{ {{ fr }}.email }}
</a>
</p>
{% endif %}
{% if({{ fr }}.phone != null) %}
<p>
<span class="label">Phone: </span>
{{ {{ fr }}.phone }}
</p>
{% endif %}
{% endmacro %}
If necessary, I can use fr.email, fr.phone, fr.* ... in each template, so maybe inheritance will work?
So my question is: is there a way to make this part of code macro and if yes will it be better or inheritance will be better?
In your situation, rather than a using a macro I would just include a twig file, using the with option. For example, you can do:
{% include 'AcmeDemoBundle:Demo:showFriend.html.twig' with {'fr': friend} %}
and showFriend.html.twig will be:
{% if(fr.email != null) %}
<p>
<span class ="label">Email Address: </span>
<a class="email" href="{{ path('friend_email', {'id': fr.id}) }}">
{{ fr.email }}
</a>
</p>
{% endif %}
{% if(fr.phone != null) %}
<p>
<span class="label">Phone: </span>
{{ fr.phone }}
</p>
{% endif %}
Within the macro you can just use fr.var so
{% if({{ fr }}.email != null) %}
will become
{% if fr.email %}
As you can see, specifying != null is not required (I even doubt if it'll work, it probably should be fr.email not is null )

Resources