Changing form data after failed validation in Symfony2 - symfony

How can I change view data after failed validation (I want to change a checkbox value while displaying validation errors to explain why you can't select it). I suppose form events doesn't help here as the validation happens in the end.

The following code snippet from the Symfony's from bootstrap layout shows how you can check whether form field is valid or not:
{% block form_row -%}
{% spaceless %}
<div class="form-group{% if (not compound or force_error|default(false)) and not valid %} has-error{% endif %}">
{{ form_label(form) }}
<div class="{{ block('form_group_class') }}">
{{ form_widget(form) }}
{{ form_errors(form) }}
</div>
</div>
{% endspaceless %}
{%- endblock form_row %}
Look at if (not compound or force_error|default(false)) and not valid you can use same check in your code.

In short, you should add boolean value to your model, add checkbox type to your FormType representing this value and set it to false when validation fails, like this:
$form->setData($model);
if (!$form->isValid()) {
$model->setFlag(false);
} else {
// save model, redirect etc
}

Related

Overriding checkbox form field producing duplicate label and no field in Symfony 4.4

I am having trouble overriding a checkbox field in my Symfony form inside my Twig template.
I am building the field with the following code in my Twig template:
{{ form_row(form.legal, {
'label' : 'personal.form.fields.legal'|trans,
}) }}
In the same template I have the following block where I am attempting to customise the label. Note the translation above includes HTML which is why I need the raw filter.
{% block _crmpiccobundle_details_legal_label %}
{% apply spaceless %}
<label{% with { attr: label_attr } %}{{ block('attributes') }}{% endwith %}>
{{ label|unescape|raw }}
</label>
{% endapply %}
{%- endblock %}
Unfortunately, this doesn't work and bizarrely leaves me with no checkbox and a duplicate label and I can't work out why.
Looks like you are using a bootstrap based form theme (like bootstrap_4_layout.html.twig or bootstrap_3_layout.html.twig)
Try doing like this:
{% block _crmpiccobundle_details_legal_label %}
{%- if widget is defined -%}
{{ widget|raw }}
{% apply spaceless %}
<label{% with { attr: label_attr } %}{{ block('attributes') }}{% endwith %}>
{{ label|unescape|raw }}
</label>
{% endapply %}
{%- endif -%}
{%- endblock %}
In bootstrap layout, widget part needs to be wrapped into label, so the theme calls the same block twice, first for _label part and second time for _widget. For the second call, theme provides widget variable, which you have to render yourself (otherwise you wouldn't see your checkbox). Also, you have to suppress label being rendered twice, which could be done just by checking whether widget is defined.
See how the original block also checks whether widget is defined to avoid double label rendering:
https://github.com/symfony/symfony/blob/e2f430dfb4c0c8cdde01ed111f4f0851e268ab5a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig#L83

CSS for Symfony2 forms: "Hello World" equivalent

How to alter the fields.html.twig template?
I am only looking for the "hello world" equivalent to see the first ever change to the form.
What's working (within a bundle view as add.Article.html.twig):
<div class="well;">
<form method="post" {{ form_enctype(form) }}>
{{ form_widget(form,{attr: {class:'test'}} ) }}
<input type="submit" class="btn btn-primary" />
</form>
</div>
The test class is only doing a CSS .test{background-color:grey;}. I am normally using Bootstrap.
In fields.html.twig I have:
{% block aliquam_input %}
{% spaceless %}
<div>
{{ form_label(form) }}
{{ form_errors(form) }}
{{ form_widget(form, { 'attr': {'class': 'test'} }) }}
</div>
{% endspaceless %}
{% endblock %}
and in app/config/config.yml:
# Twig Configuration
twig:
//..
form:
resources:
- 'AliquamSummaryBundle:Form:fields.html.twig'
The fields.html.twig is rendered ok to the main twig as I get an error when removing one of the hash in the fields twig.
What's not working (i.e. class test doesn't show) is when I try to make the fields.html.twig take effect on Article.html.twig:
{% block aliquam_input %}
{% spaceless %}
<div class="well;">
<form method="post" {{ form_enctype(form) }}>
{{ form_widget(form) }}
<input type="submit" class="btn btn-primary" />
</form>
</div>
{% endspaceless %}
{% endblock %}
As all forms will have the same format, I'd prefer to not set all html attributes to every single line. But what is the way to get the class "test" taking effect from the fields.html.twig template?
___________________________ Update for David _____________
Thanks #David, but unfortunately adding the name as form.name doesn’t change anything. The CSS attribute is still not transferred to the form in html. Well spot for the semicolon after well; it was a late night typo, but not related to the problem.
Here is what I have figured so far. I might have to end up having to enter bootstrap CSS to each individual row in a form (horrible thought) or figure how to do this by entities options, which comes almost to the same horrible thought as doing it for every row. Since the doc offers to enter a template for all forms via app/config/config.yml there should be a far simpler way, but I don’t get it.
The below is the only direction so far which made CSS work a little, i.e. class .test{background-color: black;} is doing its job:
{# ..\addArticle.html.twig #}
<div class="well">
<form method="post" {{ form_enctype(form) }}>
{{ form_widget(form) }}
<input type="submit" class="btn btn-primary" />
</form>
</div>
NB: there is no {% block .. %} around the div, which makes sense as I put the fields.html.twig to be included to the symphony standard widgets through app/config/config.yml. The above goes with the fields.html.twig below.
{# ..\fields.html.twig #}
{% block widget_attributes %}
{% spaceless %}
{{" class=\'test\'" }}
{% endspaceless %}
{% endblock widget_attributes %}
So far only fields with textarea show black color, bit it’s kind of “hello world”. I am not sure if this is right way to proceed as I have not found anything alike in this and other forums and the doc only states to look at form_div_layout.html.twig on git to decide which attribute to change, which is not really helpful (to me).
First change your well; class to well in your add.Article.html.twig.
In order to put a class on your widget I think you can try
{{ form_widget(form.name, {'attr': {'class': 'test'}}) }}
The difference with your code is the form.name parameter. If you only give the form it will not apply passed options. See the doc
Changing the fields.html.twig as
{# ..\fields.html.twig #}
{% block widget_attributes %}
{% spaceless %}
{{" class=\'test\'" }}
{% endspaceless %}
{% endblock widget_attributes %}
is indeed correct in what concerns Symfony and the reason that with bootstrap-CSS only the textareas show the class="test" is that bootstrap assigns mandatorily (all other than textareas) with the bootstrap specific one.
As I don't want to alter the bootstrap as such I ended up assigning the attributes per widget from within the entities - anything else would probably opening a can of worms.

How to customize the submit button rendering?

I'm using symfony 2.4 and I need to set my own theme for just the submit button within a form. I was already able to customize the way input fields are rendered, the following code is working.
{% block form_row %}
{% spaceless %}
<div class="form-group">
{{ form_errors(form) }}
{{ form_label(form) }}
{{ form_widget(form, { 'attr': {'class': 'form-control'} }) }}
{% if help is defined %}
<span class="help">{{ help }}</span>
{% endif %}
</div>
{% endspaceless %}
{% endblock form_row %}
Question: Which block do I need to override to accomplish my goal?
As explained in the How to customize Form Rendering section of the documentation. The submit button related blocks that you should override in order to customise your form's button are {% block submit_widget %} and {% block button_widget %} depending on the level of customisation you want to introduce.
Check the form_div_layout.html.twig code to fully understand the default implementation of those helpers.
Good to know,
Buttons support in Forms added to Symfony 2.3.

Symfony 2: "File" field value is always empty

I'm attempting to make a form that builds a slider. It can have any number of images and I'd like to show a preview of already-uploaded images. Getting the multiple image field set up was easy enough, but I'm getting caught up on showing a preview of the image.
I'm using this template to render the "Slider Image" field:
{% block form_widget_simple %}
{% spaceless %}
<div class="form-widget slider">
{% set type = type|default('text') %}
{% if type == 'file' and value is not empty %}
<img src="{{ value }}" width="200"/><br/>
{% endif %}
<input type="{{ type }}" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ value }}" {% endif %}/>
</div>
{% endspaceless %}
{% endblock form_widget_simple %}
The value variable is always empty on file input types, so I'm not sure how I can get at the url of the uploaded images. I am using a custom field type that simply adds a file field and hooks up the data source (which is just a simple wrapper around Symfony\Component\HttpFoundation\File\File). If you need this code let me know, but its all boilerplate stuff so I doubt you do.
Thanks in advance.
Symfony2 FileType doesn't have value, its owerwritten in buildView method.
https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Extension/Core/Type/FileType.php#L28
But you access it via forms.vars.data
{% if type == 'file' and form.vars.data is not null %}
{# Here you should somehow generate url to file but let assume
you have it in "/path/to/file/" folder that is accessible through web server #}
<img src="/path/to/file/{{ form.vars.data.name }}" /><br/>
{% endif %}

Symfony Twig customize an Individual field for a collection

In the documentation there is a way in Symfony to customize a Individual field, based on the name/id of the widget.
{% form_theme form _self %}
{% block _product_name_widget %}
<div class="text_widget">
{{ block('field_widget') }}
</div>
{% endblock %}
{{ form_widget(form.name) }}
Here, the _product_name_widget fragment defines the template to use for the field whose id is product_name (and name is product[name]).
This works for a normal widget, but not if a widget is inside a collection. Because of the extra columns. Like this:
name="productbundle_product_type[foobar][1][value]" id="productbundle_product_type_foobar_1_value"
What is the way to make the Twig customization work inside the collection?
I thought something like this, but that doesn't work:
{% for db in edit_form.list %}
{% block _productbundle_product_type_foobar_{{ db.name }}_widget %}
<div class="text_widget">
{{ block('field_widget') }}
</div>
{% endblock %}
{% endfor %}
Even the following doesn't work:
{% _productbundle_product_type_foobar_1_value_widget %}
What is the way to make it work?
I was working on a project a couple of evenings ago and encountered precisely this problem - the solution I found is a pair of blocks that look like this (stripped of project-specific code):
{# Collection markup #}
{% block my_collection_widget %}
{# Customise collection row prototype for allow_add #}
{% if prototype is defined %}
{% set data_prototype = block('my_collection_item_widget') %}
<div id="my_prototype" data-prototype="{{ data_prototype }}" style="display: none"></div>
{% endif %}
{% for prototype in form %}
{{ block('my_collection_item_widget') }}
{% endfor %}
{% endblock my_collection_widget %}
{# Collection row markup #}
{% block my_collection_item_widget %}
{# Collection contains simple, single type #}
{{ form_errors(prototype) }}
{{ form_label(prototype) }}
{{ form_widget(prototype) }}
{# OR #}
{# Collection contains a compound type, render child types independantly #}
{{ form_errors(prototype.inner_widget) }}
{{ form_label(prototype.inner_widget) }}
{{ form_widget(prototype.inner_widget) }}
{% endblock my_collection_item_widget %}
I know this is old question, but maybe people still happening upon this. This is explained fragment naming for collections.
You use _entry_ in these cases in place of the collection element number. Use the instructions in the link for fragment naming, but this might vary. Sometimes 'type' is part of the fragment's name, sometimes first letter is upper case, sometimes lower case, etc. I would use a browser developer tool to find the actual name to make sure. You might also be able to customize the names used by adding the getBlockPrefix function to the form class.
Therefore, in your case above, the customized block might look something like:
{% block _ProductBundle_product_entry_widget %}
<div> {{ form_row(form.field)}} </div>
{% endblock %}
Where 'field' would be the name of a field in the element of your collection.
A few things here:
1. Documentation Error
The docs seem to be a bit off with CamelCase entities. As for 5.3 it should be: _taskManager_taskLists_entry_widget (instead of _task_manager_task_lists_entry_widget)
You can confirm the naming by dumping the form or form field: {{ dump(form) }} or {{ dump(form.yourField) }} in your template and then look for unique_block_prefix within the vars section.
2. Do NOT use the block overrides, macros are much more easy
It makes things absolutely more complicated than necessary. Simply define a macro:
{% import _self as formMacros %}
{% macro formatCollection(form) %}
<div class="form-group row">
<div>
{{ form_widget(form.field1) }}
{{ form_widget(form.field2) }}
{{ form_widget(form.field3) }}
</div>
</div>
{% endmacro %}
then simply run it on each collection:
{% for item in form.collectionItems %}
{{ formMacros.formatCollection(item) }}
{% endfor %}
3. Prototype
Get the prototype before rendering the collection field. You can store it in a variable.
{% set prototype = form.collection.vars.prototype %}
Then simply render it whenever you like using our macro:
<div data-js="collection" data-prototype="{{ formMacros.formatCollection(prototype)|e('html_attr') }}">

Resources