Symfony and Twig: conditions in templates - php

I have two bundles I created by my own: one to generate admin sections (AdminBundle), and another one to create bills (BillingBundle). BillingBundle has basically 2 entities: Client and Bill (1:m).
If I install both bundles to generate the new/edit forms and lists for Client and Bill at the admin, I include a part at config.yml like this:
my_admin_bundle:
entities:
project: #whatever entity. Project at AppBundle for example
new: true
list: true
client:
new: true
list: true
bill:
new: true
list: true
On the other hand, on AdminBundle I have a template called form.html.twig which renders the form to create new elements. Basically it is just like this:
{{ form(form) }}
The doubt: since the form to create new Bills need a .js file (accounting.js), how to include it only for new Bill form but not for new Client or Project forms ?
As you can imagine, now I have this in form.html.twig:
{{ form(form) }}
<script src="/bundles/ziiwebbilling/js/accounting.js" type="text/javascript"></script>
but I don't need accounting.js at form.html.twig for new Projects, or new Clients forms.
NOTE: sorry if I extend too much with my explanation, but I don't know how to explain it in another way. Even the title of the question is awful, please edit it if you find something better.
EDIT: as I've just wrote at the first comment for jkucharovic's answer, what I'm really looking for to solve my problem, is a non-intrusive way, that is: I wouldn't like to add the line about accounting.js inside an AdminBundle template. Or maybe am I looking for "too much"?

Use custom Twig extenstion with test function like this one:
public function getTests()
{
return [
new \Twig_SimpleTest('bill', function ($var) {
return $var instanceof Bill;
})
];
}
And then in template, you can simply test if form is for Bill entity:
{{ form(form) }}
{% if form.vars.value is bill %}
<script src="/bundles/ziiwebbilling/js/accounting.js"></script>
{% endif %}
Edit: If you don't want to modify template inside bundle for certain reason, you can make loading application-wide by defining own template and overriding default form block:
{%- block form -%}
{% if form.vars.value is bill %}
<script src="/bundles/ziiwebbilling/js/accounting.js"></script>
{% endif %}
{{ form_start(form) }}
{{- form_widget(form) -}}
{{ form_end(form) }}
{%- endblock form -%}

Related

How to prevent Twig from adding form-control class to the fields it generates?

I have this code:
{% for key, customField in customFields %}
{{ form_widget(formVirtualTerminal['cf_' ~ key], { 'attr': {'placeholder': customField['FieldDisplayName'], 'name': customField['FieldName'], 'class' : 'row no-gutters mb-3'} }) }}
{% endfor %}
which traverses an array and outputs some fields for it. However, for reasons unknown to me, a form-control class is generated into all these fields and that ruins the design of the page.
In this question (delete form-control in form-row symfony/twig) it's mentioned that this is part of a default Bootstrap theme and they discuss how this theme can be deactivated on application level.
However, I prefer not to do such radical changes if possible. I would like to tell Twig not to add classes.
The following is a hack which solves the problem by removing that class:
$("#custom-text-field-container .form-control").removeClass("form-control");
However, I would like to avoid writing Javascript to remove classes from fields which should never had that class in the first place.
Is there a way to tell twig not to write form-control class into these text fields?
You can't prevent it without making changes to the theme
You can't "tell twig not to add classes".
The form_widget does not accept any parameter to remove a class or attribute that will be later applied by the theme.
Basically, it has no control over what the template does, it only passes the template whatever parameters you want, and it's up to the form template how to use that information.
E.g. for HTML classes, the usual approach is simply to merge whatever classes you pass with whatever classes the template has set by default.
Workaround while keeping most of the form theme you are using
A simple recourse would be to create to your theme based on the form theme you are using (which seems to be 'bootstrap_4_layout.html.twig')
You'd need to rewrite only the parts that hinder you, leaving everything else as it is.
E.g. a "complete" new theme could be as short as:
{# templates/form/your_theme.html.twig #}
{% use 'bootstrap_4_layout.html.twig' %}
{% block form_widget_simple -%}
{% if type is not defined or type != 'hidden' %}
{%- set attr = attr|merge({class: (attr.class|default('') ~ (type|default('') == 'file' ? ' custom-file-input' : ''))|trim}) -%}
{% endif %}
{%- if type is defined and (type == 'range' or type == 'color') %}
{# Attribute "required" is not supported #}
{%- set required = false -%}
{% endif %}
{{- parent() -}}
{%- endblock form_widget_simple %}
This is almost the same form_widget_simple block as the original, I simply removed the form-control class that is merged with the classes you can pass through.
You can use this form only in the templates you want this custom theme by doing this in the appropriate template:
{% form_theme form 'form/your_theme.html.twig' %}
Or if you want to use it by default in all forms in your application:
# config/packages/twig.yaml
twig:
form_themes: ['form/your_theme.html.twig']
# ...

Difference between customizing form theme and referencing block in Symfony?

what is the difference between referencing a widget block and customizing it : the docs say :
So far, to override a particular form block, the best method is to
copy the default block from form_div_layout.html.twig, paste it into a
different template, and then customize it. In many cases, you can
avoid doing this by referencing the base block when customizing it
But to me it looks the same :
{# app/Resources/views/Form/fields.html.twig #}
{% extends 'form_div_layout.html.twig' %}
{% block integer_widget %}
<div class="integer_widget">
{{ parent() }}
</div>
{% endblock %}
{# app/Resources/views/form/fields.html.twig #}
{% block integer_widget %}
<div class="integer_widget">
{% set type = type|default('number') %}
{{ block('form_widget_simple') }}
</div>
{% endblock %}
What is the difference?
The first example in the documentation that you are referencing shows how to override the entire widget that displays your form element.
The second example in the documentation that you are referencing shows how you can employ code re-use so that you are not rewriting form templating sections that you are not modifying. So, instead of having to declare
{% set type = type|default('number') %}
{{ block('form_widget_simple') }}
all over again in your overriding widget, you can instead reference the base block that already has this. If you are referencing base blocks from an external template, you can call the parent block via {{ parent() }}, and if you are referencing blocks from inside the same template as the form, you can call the base block via {{ block('base_integer_widget') }}
If you look at it from a PHP/Symfony point of view with inheritance that can help explain it as well. Say you have one PHP class that extends another and you want to override a function named doSomething() - you might rewrite the entire function as you need it. But, say that doSomething() has a block of common code that you always want to run, then you might perform your actions and call parent::doSomething() at the end of it. Or, if you're accessing a different Symfony service you might call $this->get('some.service')->doSomething() instead.
That's the same concept here, you can either override the entire widget or you can override parts of it - perhaps putting a surrounding <div></div> but calling {{ parent() }} from within that since you're changing nothing else about the widget.
I do have one example where I overrode standard button behavior in Symfony and used both cases. I have a separate template file in `app/Resources/views/Form/navigationButton.html.twig' with the following code:
{% use 'form_div_layout.html.twig' %}
{% block button_widget -%}
{% set attr = attr|merge({class: (attr.class|default(''))|trim}) %}
{{- parent() -}}
{%- endblock %}
{% block button_row -%}
{{- form_widget(form) -}}
{%- endblock button_row %}
I am overriding the button widget by allowing additional classes to be passed as attributes and then calling the parent widget to produce it as normal. I then override the button row widget to not put surrounding <div></div> tags since I didn't want that in my template (see the originals here and here).
Then to use in one of my templates I simply do:
{% form_theme form ':Form:navigationButton.html.twig' %}

Inherit dynamic template in Phalcon Volt

I need to load a page, that will be "inserted" in a template - as I read it, Volt's Template Inheritance should do the trick and it does... kinda. Hardcoded values, as shown in the examples, work fine - the following example works:
<!-- Template -->
<div id="site_content">
{% block test %}
{% endblock %}
</div>
and the page, that inherits the template:
{% extends "../../templates/de/index.volt" %}
{% block test %}
{{ content() }} {# this is a registered volt function that outputs the generated content #}
{% endblock %}
However, the same page might need to inherit a different template and that must be decided on runtime, so the name of the template must be generated dynamically. Two options occurred to me:
Set the template name to a variable and use it when extending - the problem here is that I don't see a way to use it afterwards. That guy seems to have had the same problem, but there is neither an answer of how to do it, nor a confirmation that it isn't possible at all.
Register another function to generate the complete string (e.g. {% extends "../../templates/de/index.volt" %}) and then compile it, e.g.
$compiler->addFunction('get_template',
function ($resolvedArgs, $exprArgs) use ($volt) {
return $volt->getCompiler()
->compileString('{% extends "../../templates/de/index.volt" %}');
});
and then use that function in the page, e.g.
{{ get_template() }}
{% block test %}
{{ content() }}
{% endblock %}
However, using that approach does not parse the page content (e.g. the content returned by the registered content() function is not shown). I'm also open to other solutions (using Twig instead of Volt is only a last resort, for performance issues), advices of what I'm doing wrong or pointers of useful articles on the topic. Thanks in advance!
Try using partials as documented in the Phalcon doc: Using Partials

Twig extend template on condition

I use Symfony 2 with Twig and my question is pretty straightforward:
In a view I want to extend one of the layouts based on a variable. If the variable is false I want to extend UdoWebsiteBundle::layout.html.twig and if it's true I want to extend UdoWebsiteBundle::layout_true.html.twig.
Here is the code I tried:
{% block layout_extender %}
{% if intro == 'false' %}
{% extends 'UdoWebsiteBundle::layout.html.twig' %}
{% else %}
{% extends 'UdoWebsiteBundle::layout_true.html.twig' %}
{% endif %}
{% endblock %}
I get this error:
Multiple extends tags are forbidden in "UdoWebsiteBundle:home:home.html.twig" at line 7
Is there any other way to achieve this?
Try this one:
{% extends intro == 'false'
? 'UdoWebsiteBundle::layout.html.twig'
: 'UdoWebsiteBundle::layout_true.html.twig' %}
Idea taken from here: http://jorisdewit.ca/2011/08/27/extending-different-layouts-for-ajax-requests-in-twig-symfony2/
To keep it neat you should use Twig dynamic inheritance support by using a variable, defined in your controller, as the base template:
{% extends parent_template_var %}
If the variable evaluates to a Twig_Template object, Twig will use it as the parent template.
Define parent_template_var in your controller:
if($intro == 'false')
$parent_template_var = 'UdoWebsiteBundle::layout.html.twig';
}else{
$parent_template_var = 'UdoWebsiteBundle::layout_true.html.twig';
}
return $this->render('::/action.html.twig', array('parent_template_var' => $parent_template_var ));
http://twig.sensiolabs.org/doc/tags/extends.html
Answer from the official documentation:
Conditional Inheritance
As the template name for the parent can be any valid Twig expression, it's possible to make the inheritance mechanism conditional:
{% extends standalone ? "minimum.html" : "base.html" %}
In this example, the template will extend the "minimum.html" layout template if the standalone variable evaluates to true, and "base.html" otherwise.
You cannot extends multiple template, that's why you've got the error, if you want to so, you need to push them in an array like below.
{% extends ['MyAppCustomBundle::Layout/layout.html.twig', 'FOSUserBundle::layout.html.twig'] %}
But you will need to use Twig version 1.2 to do it.
twig documentation
This all makes sense to do either this template or that template.
But let me describe another situation. You have a profile form and a form where users can upload personal profile related documents. Since the profile form is already very long the documents moved to a new form.
Everything works great. Now we want to use the bootstrap tabs to do Profile | Documents for user friendliness.
Now I know because we are using two seperate forms if you submit the documents the changes on the profile won't save and vice versa.
I have added the document form in the tab using
<div role="tabpanel" class="tab-pane" id="documents">
{{ render(controller('ManyAppBundle:Document:createDocument', {'viewOnly': true})) }}
</div>
The 'viewOnly': true is a query parameter and is not required by the action.
My question now becomes if the profile tab renders the document template it must only show the upload widget and the submit where as when you go directly to the document page it must show the title and side bar and everything. So I did try
{% if not viewOnly %}
{% extends ... %}
{% endif %}
That gave problems because you can't use extends within a if. Like you suggested in other answers try using
{% extends viewOnly == true ? ... %}
This reolved the Twig issue up to the execution of the code when viewOnly is false.
When viewOnly is false it must extend the base template used by all other templates but if it is true I only want to show this:
{{ form_start(form, { 'style': 'horizontal', 'col_size': 'sm' }) }}
{% if form.documents is defined %}
{{ form_row(form.documents) }}
{% endif %}
{{ form_row(form.submit, { 'attr': { 'class': 'btn btn-success' } }) }}
{{ form_end(form) }}
But now with the top
{% extends viewOnly == true ? ... %}
if viewOnly becomes false it fails with Template "" can't be find.
Is there a way to say extends this specific template that will be the same result of not extending any template?
Or alternatively is there a way of saying extend this when viewOnly true but nothing happens on the fail?

How to get a Doctrine2 Entity method from a Symfony2 Form in Twig

I'm in a Twig template, and I have a "form" variable that represents a Doctrine2 Entity Form.
This Entity has properties that are mapped into the form, but the Entity has also some methods that I would like to access from my Twig template.
I would love to do something like this:
{{ form.myMethod }}
or maybe something like this:
{{ form.getEntity.myMethod }}
but unfortunately it doesn't work.
How could I achieve what I need?
To access your entity from your FormView in a twig template you can use the following code
{{ form.get('value') }}
Where form is your FormView object. This will return your entity and from there you can call any methods on it. If you embed a collection of entities or a single entity in your form you can access it the same way
{{ form.someembedform.get('value') }}
or
{% for obj in form.mycollection %}
{{ obj.get('value').someMethod }}
{% endif %}
An even more convenient syntax to get the underlying entity instead of:
{{ form.get('value') }}
is this:
{{ form.vars.value }}
Then you can call any entity method like this:
{{ form.vars.value.someMethod }}
See also the Form chapter in the Symfony documentation.
Just in order to update the subject:
form.get('value')
is deprecated since symfony 2.1. Copy from Symfony\Component\Form\FormView :
/*
* #deprecated Deprecated since version 2.1, to be removed in 2.3. Access
* the public property {#link vars} instead.
*/
public function get($name, $default = null) ....
so, I guess
form.vars.value.youMethod()
should be the way to go. It has worked form me.
... and there it goes my first post here. hope it helps!
Lost few hours trying to figure out what's going on and why
{{ form.vars.value }}
is NULL.
If you have form.element (not the form object itself) object, for example if you are overriding a form_row template that has passed the form.row object, you can get the Entity like this:
{{ form.getParent().vars.value.MyEntityMethod }}
hope that helps someone!
EDIT: Year and so later - another useful tip:
{% block sonata_type_collection_widget %}
{% for child in form %}
{{ child.vars.form.vars.value.name }}
{% endfor %}
{% endblock %}
object methods should work in twig, I know I used them in some project.
try to use ()
like {{ form.myMethod() }}
It seems that at some point the value is actually null. So you can use
{{ (form.vars.value != null) ? form.vars.value.yourEntityMethod():'' }}
tested in SF v3.0.6.
None of the above worked for me in version 2.6.7. I used customised form widgets to achieve this:
{# src/AppBundle/Resources/views/Form/fields.html.twig #}
{% extends 'form_div_layout.html.twig' %}
{%- block entity_widget -%}
<div {{ block('widget_container_attributes') }}>
{%- for n, child in form %}
{{- form_widget(child, {
'entity': form.vars.choices[n].data
}) -}}
{{- form_label(child) -}}
{% endfor -%}
</div>
{%- endblock %-}
{%- block radio_widget -%}
{# You now have access to entity #}
{%- endblock %-}
use {{ form.getData.myMethod }}.

Categories