SonataAdminBundle templates - list and show fields template content duplication - symfony

My list and show fields contain the same content, but due the extending of base_list_field and base_show_field templates I have to create two separate templates.
list/location.html.twig:
{% extends 'SonataAdminBundle:CRUD:base_list_field.html.twig' %}
{% block field %}
{{ object.getCity }}, {{ object.getCountry.getName }}
{% endblock %}
show/location.html.twig:
{% extends 'SonataAdminBundle:CRUD:base_show_field.html.twig' %}
{% block field %}
{{ object.getCity }}, {{ object.getCountry.getName }}
{% endblock %}
As you can see huge part of code is duplicated. Is there a way to check the page I am currently in in twig and then extend certain base template? In this case I would be able to use one file instead of two for same content.

In twig it's possible to extend/include a dynamic template :
{# do test here on which page you are or pass through controller #}
{% if true %}
{% set template = 'SonataAdminBundle:CRUD:base_show_field.html.twig' %}
{% else %}
{% set template = 'SonataAdminBundle:CRUD:base_list_field.html.twig' %
{% endif %}
{% extends template %}
{% block field %}
{{ object.getCity }}, {{ object.getCountry.getName }}
{% endblock %}
(edit) if you don't want the hardcoded if I would pass the template variable through the controller and change the twig template to something like
{% extends template|default('SonataAdminBundle:CRUD:base_show_field.html.twig') %}

Related

Include Twig template which Extends

I am rendering a Twig template as below:
$this->render('base.html.twig');
The content (simplified) of this Twig template looks as below:
{% block headers %}
...
{% endblock %}
{% block pagecontent %}
...
{# I want to include another template (A) here #}
{# I want to include another template (B) here #}
{% endblock %}
{% block footers %}
...
{% endblock %}
I have another Twig template which I am not rendering, but I want to include in the above template (where I have placed my Twig comment). The content is as follows:
{% extends '::base' %}
{% block headers %}
{{ parent() }}
{% endblock %}
{% block pagecontent %}
{{ parent() }}
...
{% endblock %}
I want to eventually render several Twig templates inside of base.html.twig.
Is what I am attempting to do achievable, and if so, how do I achieve it?
You just need to render child template (the one extending base.html.twig).
Change in your controller:
$this->render('child_template_extending_base.html.twig');
Replace child_template_extending_base with your real template name.
You can also embed another controllers views in your template with this code:
{{ render(controller(
'AppBundle:Article:recentArticles',
{ 'max': 3 }
)) }}
Read more about this feature here: http://symfony.com/doc/current/book/templating.html#embedding-controllers
base.html.twig
{% block headers %}
...
{% endblock %}
{% block pagecontent %}
...
{# I want to include another template (A) here #}
{# I want to include another template (B) here #}
{% endblock %}
{% block footers %}
...
{% endblock %}
Your controller:
$this->render('base.html.twig');
Normally, $this->render('view.html.twig'); accepts only one twig.
If you want to have several templates, you can build it like this:
view.html.twig
{% extends '::base' %}
{% block pagecontent %}
{# Controller function with template 1 #}
{{ render(controller('AppBundle:Article:recentArticles',{ 'max': 3 })) }}
{# Controller with template 2 #}
{{ render(controller('AppBundle:Article:relatedArticles',{ 'max': 4 })) }}
{% endblock %}
ANOTHER POSSIBLE SOLUTION IS :
You can break one block into several blocks.

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

Custom form field template with twig

I'd like to create a custom template in twig to render a form field.
Example:
{{ form_row(form.field) }}
This can be overriden by form theming
{% block form_row %}
... custom code
{% endblock form_row %}
What I would like to do is this:
{% block custom_row %}
... custom code
{% endblock custom_row %}
and use it like this:
{{ custom_row(form.field }}
however, this throws an exception that method custom_row is not found.
My understanding is that this can be done with Twig extension, but I don't know how to register a block to be a function.
Update
what I actually want:
I use twitter bootstrap and a bundle which overrides all the form themes. And it renders a div around a radio, so it can't be inlined. So I wanted to do something like this:
copy their template and get rid of the div:
{% block inline_radio_row %}
{% spaceless %}
{% set col_size = col_size|default(bootstrap_get_col_size()) %}
{% if attr.label_col is defined and attr.label_col is not empty %}
{% set label_col = attr.label_col %}
{% endif %}
{% if attr.widget_col is defined and attr.widget_col is not empty %}
{% set widget_col = attr.widget_col %}
{% endif %}
{% if attr.col_size is defined and attr.col_size is not empty %}
{% set col_size = attr.col_size %}
{% endif %}
{% if label is not sameas(false) %}
{% if not compound %}
{% set label_attr = label_attr|merge({'for': id}) %}
{% endif %}
{% if required %}
{% set label_attr = label_attr|merge({'class': (label_attr.class|default('') ~ ' required')|trim}) %}
{% endif %}
{% if label is empty %}
{% set label = name|humanize %}
{% endif %}
{% set label_attr = label_attr|merge({'class': (label_attr.class|default('') ~ ' radio-inline')|trim}) %}
<label{% for attrname, attrvalue in label_attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %}>
{{ block('radio_widget') }}
{{ label|trans({}, translation_domain) }}
</label>
{% else %}
{{ block('radio_widget') }}
{% endif %}
{{ form_errors(form) }}
{% endspaceless %}
{% endblock inline_radio_row %}
and then
{{ inline_radio_row(form.field) }}
I ended up just overriding the whole theme, and added ifs around the div in question, a the class (radio-inline). But I'm still wondering if there's a way to make this work. Seems like it makes you work so hard for something so simple.
Update 2
I found the functionality:
class FormExtension extends \Twig_Extension
{
public function getFunctions()
{
return array(
'inline_radio_row' => new \Twig_Function_Node(
'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode',
array('is_safe' => array('html'))
),
);
}
}
This does exactly what I want, but it says it's deprecated. Anyone knows an updated version of how to use this?
Update 3
Similar functionality can be also achieved with http://twig.sensiolabs.org/doc/tags/include.html
You can use the twig functions for each part of a form row:
form_label(form.field)
form_widget(form.field)
form_errors(form.field)
For example:
<div class="form_row">
{{ form_label(form.field) }} {# the name of the field #}
{{ form_errors(form.field) }} {# the field #}
{{ form_widget(form.field) }} {# the errors associated to the field #}
</div>
You can use form theming.
Step by step:
1. Form Type Class
Check the name in your class
public function getName() {
return 'hrQuestionResponse';
}
2. Include a custom theme in your template
{% form_theme form 'InterlatedCamsBundle:Form:fields.html.twig' %}
3. Find the block
Can be quite difficult. For the bootstrap bundle, as you seem to have found it is in ./vendor/braincrafted/bootstrap-bundle/Braincrafted/Bundle/BootstrapBundle/Resources/views/Form/bootstrap.html.twig and you have found the block radio_row. I have been finding the block by putting output in the source template and overriding more blocks than I need. In 2.7 there is a theme 'rendering call graph'.
4. Override the block
Copy the block from the master template and call it replace the standard term with the name of in the FormType found in step 1.
Don't forget to also change the endblock name.
e.g.
{% block hrQuestionResponse_widget %}
hrQuestionResponse_row
{% spaceless %}
{% set class = '' %}
...
{% endspaceless %}
{% endblock hrQuestionResponse_widget %}
In your case because you can only call form_widget() you will need to override _widget. You could extract only the content that you need, or you can override the block chain to radio_row.

symfony2: Applying theme to individual field for collection type

Just applying a theme to form field is easy e.g.:
{% form_theme form _self %}
{% block _product_name_widget %}
{{ block('field_widget') }}
{% endblock %}
but what if the form field is of collection type? E.g.: product['models'][1][comment,
I have no ideas how to customize it. (This may be the first kind question here)
UPDATE: anwser here: Symfony2: collection form field type with data-prototype
As of Symfony 2.1 (it may work as well for 2.0 but not sure) to apply theme for collection you do this way:
Lets say we have a collection of products (we have multiple Product entities)
Controller:
$repository = $this->getDoctrine()->getRepository('ExampleBundle:Product');
$products = $repository->findAll();
$productCollection = new Products;
foreach ($products as $product) {
$productCollection->getProducts()->add($product);
}
$collection = $this->createForm(new ProductsType, $productCollection);
return $this->render('ExampleBundle:Default:index.html.twig', array(
'collection' => $collection->createView()
));
Your theme can look like this:
{% block _productsType_products_entry_name_row %}
<div class="yourDivName">{{ block('form_widget') }}</div>
{% endblock %}
{% block _productsType_products_entry_description_row %}
<div class="yourDivDescription">{{ block('form_widget') }}</div>
{% endblock %}
The trick is with "entry" where twig will do the job for you to apply above changes to each row and for each field you specify
Hope that helps!
You can do with override collection_widget block:
{% block collection_widget %}
{% spaceless %}
{% if prototype is defined %}
{% set attr = attr|merge({'data-prototype': form_row(prototype) }) %}
{% endif %}
{{ block('form_widget') }}
{% endspaceless %}
{% endblock collection_widget %}
If you want how to customize form collection type try to look at this Product Bundle
You can override the collection_widget theme for a single field by referencing the widget like this as well.
For example, if "categories" is a "collection" widget on a ProductType form, you can do this.
{% block _product_categories_widget %}
{% for child in form %}
{{- form_row(child) -}}
<hr>
{% endfor %}
{% endblock %}

Dynamically call a macro in Twig?

Is possible to dynamically call a macro in Twig? For example, here is a template and a macro named "group" which builds a button group using buttons array argument. There are also two other macros, save and delete, for building save and delete buttons.
{# Make a group of buttons #}
{% macro group(buttons) %}
{% spaceless %}
{% import "::macros.html.twig" as macros %}
{% set content = '' %}
{% for button in buttons %}
{% set content = content ~ button %}
{% endfor %}
{{ macros.el('div', content, { 'class' : 'btn-group' }) }}
{% endspaceless %}
{% endmacro %}
{# Make a save button #}
{% macro save(attributes, size, image) %}
{% spaceless %}
{{ _self.primary('save'|trans({}, 'buttons'), attributes, size, image) }}
{% endspaceless %}
{% endmacro %}
{# Make a delete button #}
{% macro delete(attributes, size, image) %}
{% spaceless %}
{{ _self.danger('delete'|trans({}, 'buttons'), attributes, size, image) }}
{% endspaceless %}
{% endmacro %}
This works fine passing an array of buttons:
{% import "::buttons.html.twig" as buttons %}
{% set items = [buttons.save, buttons.delete] %}
{{ buttons.group(items) }}
But i'd like to pass macro names to group macro:
{% import "::buttons.html.twig" as buttons %}
{{ buttons.group(['save', 'delete']) }}
and get save and delete macros called automatically. Is this possible and how?
why not just do
{% import "::buttons.html.twig" as buttons %}
{{ buttons.group([buttons.save, buttons.delete]) }}
For those looking for an example of how to implement dynamic macro calling using attribute, checkout https://gist.github.com/tentacode/9728963b9f3a714608f3

Resources