Symfony ChoiceType deep label customization - symfony

I want to customize my EntityType's choice labels highly, like creating a table with multiple attributes of my model.
So I want to access my choices class attributes. The choices are my entities of class MyClass through the EntityType. How can I do so in twig?
Currently I do it like this:
1. in my FormClass I json_encode all fields I need in my label
2. in my template I json_decode these information and display according
IN CODE:
1.
$builder
->add('field', EntityType::class, [
'class' => MyClass::class,
'multiple' => false,
'expanded' => true,
],
'choice_label' => function (MyClass $myClass) {
$data = [
'name' => $myClass->getName(),
'description' => $myClass->getDescription(),
];
return json_encode($data);
},
])
2.
{% block my_form_widget %}
...
{# form is my 'field' FormView of the EntityType #}
{% for child in form %}
{# child is a FormView, one choice of my EntityType #}
{# child.vars.data is boolean as its a checkbox #}
{% set data = child.vars.label|json_decode %}
create some complex html here, like tables
...
{% endfor %}
...
{% endblock %}
Working. But is there a better way?
Thanks,
Kim

In a Symfony form (or form field, which is just a form of its own) that is mapped to an entity, you always have access to the underlying data in form.vars.data. So in this case, form.vars.data would either be null or an instance of MyClass.
For ease of use in your template, you might do something like:
{% set my_object = form.field.vars.data %}
{% if my_object %}
{{ my_object.getName() }}
{{ my_object.getDescription() }}
{% endif %}
Thus there is no need to re-encode your entity data for the view layer, since it's always already available.
If you are working with an EntityType and want to access the properties of each choice's entity, they are available as well in the choices array:
{% for choice in form.field.vars.choices %}
{{ choice.data.getName() }}
{{ choice.data.getDescription() }}
{% endfor %}
A nice trick when you're trying to access any form data and aren't sure where to look is to temporarily add a line to your template like:
{{ dump(form.field) }}
This will allow you to look through the available data and see what all is available. Note that it requires the Twig debug extension to be enabled, and XDebug to be enabled in PHP in order to make the output look nice.

Ok I got it, here is an example of how to access the data of your EntityType's choices in twig. You can check the child.parent.vars.choices list.
{% block my_form_widget %}
...
{# form is my 'field' FormView of the EntityType #}
{% for child in form %}
{# child is a FormView, one choice of my EntityType #}
{# child.vars.data is boolean as its a checkbox #}
{% for choice in child.parent.vars.choices if choice.value == child.vars.value %}
{{ choice.data.name }} {# contains MyClass name #}
{{ choice.data.description }} {# contains MyClass description #}
{% endfor %}
...
{% endfor %}
...
{% endblock %}

Related

KNP Menu Bundle Translation Domain

I'm using Symfony 2.3 and the KnpMenuBundle.
Is it possible to use a translation domain for menu items?
Like this:
$menu['management']->addChild(
'msg.user.list',
array(
'route' => 'user_list',
'translation_domain' => 'navigation'
)
);
According to the Symfony documentation and KnpMenuBundle documentation,you may set the translation domain (menu in my snippets), while adding menu items in your MenuBuilder class:
$menu->addChild('Home', array('route' => 'homepage'))
->setExtra('translation_domain', 'menu');
You may better want to add the translation domain into the whole menu instead:
$menu = $this->factory->createItem('root')
->setExtra('translation_domain', 'menu');
Then create a file named knp_menu.html.twig in:
app/Resources/views/menu/
and put this in it:
{% extends 'knp_menu.html.twig' %}
{% block label %}
{% if options.allow_safe_labels and item.getExtra('safe_label', false) %}
{{ item.label | raw | trans(item.getExtra('translation_params', {}), item.getExtra('translation_domain', 'menu'))}}
{% else %}
{{ item.label | trans(item.getExtra('translation_params', {}), item.getExtra('translation_domain', 'menu')) }}
{% endif %}
{% endblock %}
(If the file already exists, just replace the {% block label %}{% endblock %} part)
Be carefull to have translation files (.xliff or whatever) naming strategy like:
{translation_domain}.{locale}.{extenstion}
for example:
menu.fa.xliff
In this path:
app/Resources/translations/
The last part is:
# app/config/config.yml
knp_menu:
twig:
template: knp_menu.html.twig
Up-Vote this nice tutorial.
Have fun!

Toggle html validation globally

I've made a couple of twig extensions but I'm stumped on this one.
I have the following template logic that I want to make into an extension.
I need reuse this logic into many different forms instead of copying and pasting the following code everywhere:
{% if html5validation is not defined %}
{{ form_start(some_form) }}
{% else %}
{% if html5validation %}
{{ form_start(some_form) }}
{% else %}
{{ form_start
(
company, {'attr': {'novalidate': 'novalidate'}}
)
}}
{% endif %}
{% endif %}
With the above code from the controller I can do the following to turn the html5 validator on and off:
$this->render(..., array(html5validation => false));
I want put the template logic into the twig extension below...
I just don't know if it's possible to implement what I've done above in a twig extension.
class HTML5Validation extends \Twig_Extension
{
public function getFunctions()
{
return array(
new \Twig_SimpleFunction('html5validation', array($this, 'setValidation')),
);
}
public function setValidation($boolean)
{
//Implement the same logic as the twig template.
}
public function getName()
{
return 'html5validator';
}
}
The short answer is no - you can't do this using a twig extension, it's not what they're meant for.
Looking at your template fragment I'd say you need to customise the form_start block. To do this see Symfony Form Theming and How to customise form rendering.
EDIT: This solution does not work if your customised code requires local twig variables - only global twig variables are available for form theming. You can define your own twig globals in config.yml or in a twig extension.
For example, to override form_start globally, you find the default definition of the form_start block in form_div_layout.html.twig, copy it into your own form theme file e.g. YourBundle/Form/fields.html.twig, modify it as required and and update the twig configuration to apply your form theme file. Something like this:
{# src/YourBundle/Form/fields.html.twig #}
{% extends 'form_div_layout.html.twig' %}
{% block form_start -%}
{% if html5validation is not defined %}
{{ parent() }}
{% else %}
{% if html5validation %}
{{ parent() }}
{% else %}
{{ parent
(
company, {'attr': {'novalidate': 'novalidate'}}
)
}}
{% endif %}
{% endif %}
{%- endblock form_start %}
Config:
# app/config/config.yml
twig:
form:
resources:
- 'YourBundle:Form:fields.html.twig'
I actually found a better way to do what I wanted.
As a plus it works globally instead of having to populate more fields into your controller!
In YourBundle/Resources/views/validation.toggle.html.twig
{% extends 'form_div_layout.html.twig' %}
{% block form_start -%}
{% if html5validation is defined and html5validation == false %}
{% set attr = attr|merge({'novalidate': 'novalidate'}) %}
{% endif %}
{{ parent() }}
{%- endblock form_start %}
Then if you want to turn off html5 validation across the whole website:
# app/config/config.yml
twig:
global:
html5validation: false
Or
Even better just use it in your dev_config.yml if you want validation on by default on production mode but the ability to toggle validation on and off for dev mode.
# app/config/dev_config.yml
twig:
global:
html5validation: false
resources:
- 'YourBundle::validation.toggle.html.twig'
Finally use it in your twig template normally:
{% form_theme your_form 'YourBundle::validation.toggle.html.twig' %}
form_start(your_form)
Reusable and non invasive, exactly like I wanted it. :)
I got the hint from:
https://github.com/symfony/symfony/issues/11409#issuecomment-49358377
In the absence of a more elegant solution, you can always put the twig fragment given in your question into a separate file and use twig include from your various forms. The included fragment has access to the variables from the surrounding context:
{# YourBundle/Resources/views/form_start.html.twig #}
{% if html5validation is not defined %}
{{ form_start(some_form) }}
{% else %}
{% if html5validation %}
{{ form_start(some_form) }}
{% else %}
{{ form_start
(
company, {'attr': {'novalidate': 'novalidate'}}
)
}}
{% endif %}
{% endif %}
Then in the twig file for the form:
{% include 'YourBundle::form_start.html.twig' %}
If you typically pass a 'form' variable into render() in your controller(s) then you can use that in your form_start fragment. Otherwise you can pass the appropriate form in as a variable:
{% include 'YourBundle::form_start.html.twig' with {'form': localForm} %}

Twig variable doesn't exist in form_theme

I'm trying to override default Symfony2 form to implement the code below that will allow me to turn off html5 validation on and off by just passing a variable.
html5validation.toggle.html.twig
{% extends 'form_div_layout.html.twig' %}
{% block form_start -%}
{% if html5validation %}
{{ parent() }}
{% else %}
{{ parent
(
companyform, {'attr': {'novalidate': 'novalidate'}}
)
}}
{% endif %}
{%- endblock form_start %}
index.html.twig
form_theme company_form 'AcmeDemoBundle:html5validation.toggle.html.twig'
form(companyform)
In my controller I have:
$this->render('AcmeDemoBundle:Index:index.html.twig', array('html5validation' => false, 'companyform' => ...
If I try to dump(html5validation) inside index.html.twig I get bool(false).
But when I try to include the form_theme company_form AcmeDemoBundle:html5validation.toggle.html.twig
I get the error:
Variable "html5validation" does not exist
Can controller variables not be used inside themes?
Turns out only global variables are allowed to be used in form_theme(s) :(
Toggle html validation globally

How to reuse sonata Admin paginator with a custom crud controller

I have a custom CRUD controller with an action which retrieves a custom doctrine result set and passes it to a custom template. Is it possible to make use of Sonata Admin's pagination mechanism without reusing the standard list templates?
If so, what object format does the paginator expect? I could not see any mention of this in the documentation
The template I would need to populate with results would be base_results.html.twig:
{% block num_pages %}
{{ admin.datagrid.pager.page }} / {{ admin.datagrid.pager.lastpage }}
-
{% endblock %}
{% block num_results %}
{% transchoice admin.datagrid.pager.nbresults with {'%count%': admin.datagrid.pager.nbresults} from 'SonataAdminBundle' %}list_results_count{% endtranschoice %}
-
{% endblock %}
{% block max_per_page %}
<label class="control-label" for="{{ admin.uniqid }}_per_page">{% trans from 'SonataAdminBundle' %}label_per_page{% endtrans %}</label>
<select class="form-control per-page small" id="{{ admin.uniqid }}_per_page">
{% for per_page in admin.getperpageoptions %}
<option {% if per_page == admin.datagrid.pager.maxperpage %}selected="selected"{% endif %} value="{{ admin.generateUrl('list', {'filter': admin.datagrid.values | merge({'_page': 1, '_per_page': per_page})}) }}">
{{ per_page }}
</option>
{% endfor %}
</select>
{% endblock %}
If I want to add an additional constraint on the result set, how would I adapt the following code from Sonata's list action?
if (false === $this->admin->isGranted('LIST')) {
throw new AccessDeniedException();
}
$datagrid = $this->admin->getDatagrid();
$formView = $datagrid->getForm()->createView();
// set the theme for the current Admin Form
$this->get('twig')->getExtension('form')->renderer->setTheme($formView, $this->admin->getFilterTheme());
return $this->render($this->admin->getTemplate('list'), array(
'action' => 'list',
'form' => $formView,
'datagrid' => $datagrid,
'csrf_token' => $this->getCsrfToken('sonata.batch'),
));
The DatagridBundle is a work in progress in order to decouple the list, pagination and filtering mechanisms from the Admin for them to be reusable elsewhere. Hence it is not stable yet. I strongly recommend you use the pagination from the AdminBundle at the moment.
Regarding your main question, if you wish to understand fully how to use the pager, I recommend you take a look at the Datagrid::buildPager method (https://github.com/sonata-project/SonataAdminBundle/blob/master/Datagrid/Datagrid.php#L90).

Directly access a form field's value when overriding widget in a twig template

What I want to do is get variables stored in form view.
{% form_theme edit_form _self %}
{% block field_widget %}
{% spaceless %}
{% set type = type|default('text') %}
<input type="{{ type }}" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ value }}" {% endif %}/>
{# MY CODE #}
{% if type == "file" %}
<a class="BOpreview" href="{# NEED TO REPLACE VAR HERE #}">Aperçu</a>
{% endif %}
{# MY ATTEMPT #}
{{ form.title.get('value') }}
{{ form.vars.value.url }}
{% endspaceless %}
{% endblock field_widget %}
My form has properties like url, title, etc and I am trying to access them here to use it in the field widget block.
I searched for it and came on https://groups.google.com/forum/?fromgroups=#!topic/symfony2/onor9uFte9E that suggested:
{{ form.title.get('value') }}
{{ form.vars.value.url }}
which didn't work for me.
Note: If I do a var_dump on $form->createView() in my controller, I get:
object(Symfony\Component\Form\FormView)[331]
private 'vars' =>
array (size=15)
'value' =>
object(Panasonic\TestEtAvisBundle\Entity\Product)[168]
protected 'reviewArray' =>
object(Doctrine\ORM\PersistentCollection)[234]
...
protected 'testArray' =>
object(Doctrine\ORM\PersistentCollection)[221]
...
protected 'fbshareArray' =>
object(Doctrine\ORM\PersistentCollection)[317]
...
private 'id' => int 2
private 'name' => string 'Nom du produit' (length=14)
private 'title' => string '<span>Titre </span>' (length=19)
private 'image' => string 'bundles/testetavis/uploads/product/0d9d9550.png' (length=47)
private 'fbImage' => string 'bundles/testetavis/uploads/product/facebook//product_e928cd96.jpg' (length=65)
private 'description' => string '<span>Descriptif </span>' (length=24)
private 'url' => string 'http://www.google.com' (length=21)
private 'creationDate' =>
object(DateTime)[210]
...
private 'modificationDate' =>
object(DateTime)[209]
...
private 'isDeleted' => int 0
'attr' =>
array (size=0)
empty
'form' =>
&object(Symfony\Component\Form\FormView)[331]
'id' => string 'panasonic_testetavisbundle_producttype' (length=38)
'name' => string 'panasonic_testetavisbundle_producttype' (length=38)
'full_name' => string 'panasonic_testetavisbundle_producttype' (length=38)
I want to access that url for instance but can't seem to be able to do it after many variations. Including use of {{ value }}, {{ value.url }}
But inspite of vars, I can do {{ full_name }} and get panasonic_testetavisbundle_producttype.
Any ideas?
Edit2: I found out the real problem...
Edit3: Seeing that this question is quite popular I decided to clarify on what I attempted to do in case it helps someone in the same situation. If you rely strictly on what the question asks, as I stated from my research and that Besnik supported are indeed correct.
Now what I wanted to do is for every input type file, get url from object used to render form and append a preview link, using retrieved url, beside the input type file.
If you try to get the form var of an input type "file" like this "{{ form.vars.value.url }}" in my code, this doesn't work since, if I recall correctly, you receive a token instead of the url stored inside the object.
You can access the current data of your form via form.vars.value:
{{ form.vars.value.title }}
See Symfony2 Forms documentation: http://symfony.com/doc/current/book/forms.html#rendering-a-form-in-a-template
Dump vars by using dump function:
{{ dump(form.vars.value) }}
If you are using subforms or want to have a value of a specific field:
{{ form.FIELD.vars.VALUE }}
You can access values of the parent parent from a widget block using form.parent.vars
For example, we want to render the value from a type text field called primerNombre we will need
{{ form.vars.value.primerNombre }}
If we wanted to render the name of one of the children we will need
{% for hijo in form.hijos %}
<td><div align="left">{{ form_widget(hijo.vars.value.primerNombre) }}</div></td>
{% endfor %}
Good luck!
In Symfony > 3 you may use:
form.vars.value.Entity.someValue
Edit2:
Finally, I was indeed getting the value of the current row in {{ value }} here:
{% block field_widget %}
{% spaceless %}
{% set type = type|default('text') %}
<input type="{{ type }}" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ **value** }}" {% endif %}/>
{# MY CODE #}
{% if type == "file" %}
<a class="BOpreview" href="{{ value }}">Aperçu</a>
{% endif %}
{% endspaceless %}
{% endblock field_widget %}
But in my case I get a token instead of the value since I am using input type file. This is due to a security measure in Symfony2.

Resources