Do Swig's macros support calling nested macros, dynamically? - macros

I've created a Swig macro that dynamically calls new macros. The result I've found from doing this is that the HTML markup from within the nested macro is not rendering as HTML, rather it's rendering as an HTML text node which includes the HTML tag I used. My test markup was simply an H1 element with "Hello World!" inside the element.
Do Swig's macros support calling nested macros? Or any ideas of how I can get the HTML markup from the nested macro to render as HTML?
Here's the exact code I'm using for this test:
macros.html
Primary/parent macro (HTML renders fine):
{% macro call(macro) %}
{% if (macro.name === "transitions") %}
{% import "transitions.html" as transitions %}
<h1>Transitions!</h1>
{{ transitions[macro.method](macro.vars) }}
{% elseif (macro.name === "grid") %}
{% import "grids.html" as grids %}
<h1>Grids!</h1>
{{ grids[macro.method](macro.path, macro.vars) }}
{% endif %}
{% endmacro %}
transitions.html
Nested/child macro (String is rendered):
{% macro slider(vars) %}
<h1>Hello Slider!</h1>
{% endmacro %}

Okay, that didn't take long. Issue resolved!
Solution safe filter forces the input to not be auto-escaped:
{% macro call(macro) %}
{% if (macro.name === "transitions") %}
{% import "transitions.html" as transitions %}
<h1>Transitions!</h1>
{{ transitions[macro.method](macro.vars)|safe }}
{% elseif (macro.name === "grid") %}
{% import "grids.html" as grids %}
<h1>Grids!</h1>
{{ grids[macro.method](macro.path, macro.vars)|safe }}
{% endif %}
{% endmacro %}
Got the idea from this question: jinja2: macro selecting macro or dynamic macro calls.
Then looked up docs for "safe" on the Swig website http://paularmstrong.github.io/swig/docs/filters/#safe.
Exactly what I needed.
EDIT:
By the way, I also discovered that calling the nested macro in the normal way (macro.method) without using a dynamic key (macro[method]) worked without using the safe filter. Not what I needed for my specific situation, but just thought I'd throw that out there.

Related

Nunjucks: Passing an object as parameter in macro

I'd like to create some nested macros. One for a section, and in there I'd like to call the macro of any component dynamically. (In this case the component I want to call is article.)
Here's my section macro:
<!-- section.nunjucks -->
{% macro section(config) %}
<section class="site__section section">
{% for item in config %}
{{ item.macro(item.settings) }}
{% endfor %}
</section>
{% endmacro %}
Component macro:
<!-- article.nunjucks -->
{% macro article(settings) %}
<article class="article {{ settings.classes }}">
<h1 class="article__title">Hello World</h1>
<p class="article__body">Lorem ipsum dolor.</p>
</article>
{% endmacro %}
And i'm trying to call it here:
{{ section([{'macro': article, 'settings': {'classes': 'article--large'}}]) }}
I get a syntax error for this bit: 'settings': {'classes': 'article--large'}
How can I pass in settings.classes as a parameter, when calling my settings macro?
As I just have found out, spaces matter in Nunjucks. So writing
{{ section([{ 'macro': article, 'settings': { 'classes': 'article--large' } }]) }}
instead, will actually run without any syntax errors.

Global form attribute in Symfony 2 form

I'm using Symfony 2.4. What I am trying to do is to set form attributes on the form level which are accessible to each individual widget.
I have a custom form theme which can render the form slightly differently if it is a horizontal or vertical layout. I'd like to be able to specify { 'horizontal': true } as an attribute on the form, then be able to read it while I'm reading each widget.
Is it possible? I want to avoid needing to do this on every widget.
{{ form_row(form.person.province, {horizontal:true}) }}
{{ form_row(form.person.postalCode, {horizontal:true}) }}
{{ form_row(form.person.phone, {horizontal:true}) }}
couldn't you achieve that in your own form theme?
{% block form_row %}
{% spaceless %}
{% if horizontal|default(true) %} {# or something along these lines #}
...
{% else %}
...
{% endif %}
{% endspaceless %}
{% endblock form_row %}

Symfony2 Render individual field

I'm having a problem rendering a custom individual field.
Adding the external template globally (config.yml) works but adding the same block in the same template doesn't.
form_elements.html.twig
{% block _user_email_widget %}
<h1>test</h1>
{% endblock %}
template.html.twig (won't work)
{% form_theme form 'ProjectUserBundle:Form:form_elements.html.twig' %}
{% block content %}
{{form_widget(form.user)}}
{% endblock %}
config.yml (works)
twig:
form:
resources:
- 'ProjectUserBundle:Form:form_elements.html.twig'
Fixed
The problem was on the template:
{% block content %}
{% form_theme form 'ProjectUserBundle:Form:form_elements.html.twig' %}
{{form_widget(form.user)}}
{% endblock %}
This is the right way to do that

Symfony 2 Forms with Twig: add form variable to existing types

I need my symfony2/twig forms to adhere to a certain condition: All form rows must look similar to this:
{% block form_row %}
<div class="CONSTANT_CLASS class_based_on_field_type class_based_on_error">
{{ form_label(form) }}
{{ form_widget(form) }}
...
</div>
{% endblock form_row %}
Notice that I need to get the field type within the form_row block. Alas, the field type is only defined at the widget level.
I definitely need a way to let my form_row know what type of field it is dealing with.
So i suppose it would be best to somehow override the form_row twig function.
Where can the default twig functions be overridden? And how could this be done?
Remember, this is not about customizing a form. I need to know how to add to existing form variables for existing field types.
#nifr:
The key answer for you seems to be the {% set typeClass ... %}. But there is not a defined variable text anywhere for the template. Look at form_div_layout.html.twig at line 158ff, I think the type really gets set only at the form_widget level and is thus capsulated to be there. That means using the type at the form_row level will aways result in the given default (So it occurred to me while testing). If you can prove this wrong I will happily accept your answer.
How to override the form_row block in Twig adding attributes by field-type?
Though you say this is not about form customization it can be achieved with it ... Quick Introduction for others reading this now.
The default symfony twig form extensions can be found here.
The default twig form theme can be found at Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig.
General information on how to override forms can be found in the How to Customize Form Rendering chapter of the Book but i will sum this up shortly.
form_row Default
{% block form_row %}
{% spaceless %}
<div>
{{ form_label(form) }}
{{ form_errors(form) }}
{{ form_widget(form) }}
</div>
{% endspaceless %}
{% endblock form_row %}
Overriding Form Level
Add this to the form template you want to customize:
{% form_theme form _self %}
If you want to put the {% block form_row %} into another bundle/template use this:
{% form_theme form 'AcmeDemoBundle:Form:fields.html.twig' %}
Now insert your custom form_row block after the form_theme declaration or put it into the specified template (in our case this would be AcmeDemoBundle:Form:fields.html.twig).
In my example we will add the class 'error' if there is an error in the form row and a another classname nameely the type name of the current field-type.
{% block form_row %}
{% spaceless %}
{# set class to 'error' if errors exist #}
{% set attr = attr|merge({'class': attr.class|default('') ~ (errors|length > 0 ? ' error' : '') }) %}
{% set typeClass = ' ' ~ type|default('text') %}
{#
you could also implement a logic matching input types with an array of their
desired classname-representations here.
#}
{% set attr = attr|merge({'class': attr.class|default('') ~ type) }) %}
<div class="{% for class in attr.class %}{{ class }}{% endfor %}{{ typeClass }}">
{{ form_label(form) }}
{{ form_errors(form) }}
{{ form_widget(form) }}
</div>
{% endspaceless %}
{% endblock form_row %}
if you want to apply your form_row block system-wide add your AcmeDemoBundle:Form:fields.html.twig to your twig.templating.form.resources !
# app/config/config.yml
framework:
templating:
form:
resources:
- 'AcmeDemoBundle:Form'
In the form_row block you can use :
{{ form.vars.block_prefixes[2] }}
form.vars.block_prefixes gives you an array with more information and it might change with versions. But from what I have seen so far it is always index 2

Symfony2 Theming form generated by embedded controller action

I am generating the form in embedded controller action. And now i have faced the following problem. The Form Theming is not working in that case.
So what i have:
The tempalte "page.html.twig"
{% block content %}
{% render 'MyBundle:Widget:index' %}
{% endblock %}
The indexAction() creates the form and rendering another template "form.html.twig" which is normally renders a form using form_row, form_rest and so on.
So, now i am trying to customize form theming, and here is my problem.
When i put the code
{% form_theme form _self %}
in the page.html.twig, i got an error the the form variable does not exists. And its correct, the form var is created later in the embedded controller.
But when i put the theming code in embedded template "form.html.twig", i got another error "Variable "compound" does not exist"
{% block form_label %}
{% spaceless %}
{% 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 %}
<label{% for attrname, attrvalue in label_attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %} {% if attr.tooltip is defined %}title="{{ attr.tooltip }}"{% endif %}>{{ label|trans({}, translation_domain) }}{% if required %}<span>*</span>{% endif %}</label>
{% endspaceless %}
{% endblock form_label %}
This part of code was copied from this file https://github.com/symfony/symfony/blob/2.1/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig
So tried someone to do something like this?
Answering my question myself.
It was a small sentence in Symfony2 docs http://symfony.com/doc/current/book/forms.html
This {% form_theme form _self %} functionality will only work if your template extends another. If your template does not, you must point form_theme to a separate template.
So there are two solutions to solve this problem:
move form theme code to the separate file and include it in a embedded template using
{% form_theme form with 'fields.html.twig' %}
leave the form theme code in the same template, where the form will be generated, but extend the template from some "form.html.twig" empty template.
I have only done the second way, and its works, but I am sure the first one will work as well.