Symfony forms override field appearance - php

I'm trying to override field appearance.
I created my custom form type:
namespace AppBundle\Form\Type\Frontend\Search;
/**/
class TripTypeType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'choices' => [
'One-way',
'Round-trip',
],
'multiple' => false,
'expanded' => true,
'required' => false,
'empty_value' => false,
]);
}
/**
* {#inheritdoc}
*/
public function getParent()
{
return ChoiceType::class;
}
}
After that i added this field to parent form:
namespace AppBundle\Form\Type\Frontend\Search;
/***/
class FlightSearchType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('tripType', TripTypeType::class)
->add('airports', AirportsType::class)
->add('flightAt', FlightDatesType::class)
->add('flightClass', FlightClassType::class);
}
}
And rendering this field like that:
{% for type in form.tripType %}
{% if loop.index == 1 %}
{% set way_id = 'one-way' %}
{% else %}
{% set way_id = 'round-trip' %}
{% endif %}
{{ form_widget(type, {
'id': way_id,
'attr': {'class': 'tabs-way__radio'},
'label_attr': {'class': 'tabs-way__label', 'for': way_id}
}) }}
{% endfor %}
So what i want to get is a:
<div class="tabs-way__item">
<input type="radio" id="one-way" name="flight_search[tripType]" class="tabs-way__radio" value="0">
<label class="tabs-way__label" for="one-way">One-way</label>
</div>
For that i'm trying to override widget for this field:
{# app/Resources/views/Form/fields.html.twig #}
{% block trip_type_widget %}
<div class="tabs-way__item">
<input type="radio" {{ block('widget_attributes') }}{% if value is defined %} value="{{ value }}"{% endif %}{% if checked %} checked="checked"{% endif %} />
{{ block('form_label') }}
</div>
{% endblock trip_type_widget %}
And i include that file in config.yml in twig section. But all i get it's nothing :(

If you need to override custom field, you have to prefix it with underscore, e.g.:
{% block _trip_type_widget %}
<div class="tabs-way__item">
{{- form_label(form) -}}
{{- form_widget(form) -}}
{{- form_errors(form) -}}
</div>
{% endblock _trip_type_widget %}

Related

Symfony 2.8 form dynamic generation choice_label raw twig

I want to render raw form label, I have the following form entity type
$builder
->add('subscriptionBilling', 'entity', array(
'class' => 'AppBundle\Entity\SubscriptionBilling',
'data_class' => 'AppBundle\Entity\SubscriptionBilling',
'choice_label' => function ($allChoices, $currentChoiceKey) {
return '<p>Categories <strong>'.$allChoices->getNoOfCategories().' number</strong> '.$currentChoiceKey.'</p>';
},
'choices' => $options['packages_allowed'],
'data' => $options['selected_subscriptionBilling'],
'multiple' => false,
'expanded' => true,
'required' => true,
'label' => false,
))
;
and my twig
{% autoescape false %}
{{form_label(form_categories.subscription.subscriptionBilling)|raw}}
{{form_widget(form_categories.subscription.subscriptionBilling)|raw}}
{% endautoescape %}
but i get this html
<p>Categories <strong>5 number</strong> 0</p>
<p>Categories <strong>10 number</strong> 1</p>
<p>Categories <strong>25 number</strong> 2</p>
<p>Categories <strong>10 number</strong> 3</p>
I finally created a form theme which extends bootstrap form layout
{% extends 'bootstrap_3_layout.html.twig' %}
{% block radio_label %}
{# Do not display the label if widget is not defined in order to prevent double label rendering #}
{% if widget is defined %}
{% if required %}
{% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' required')|trim}) %}
{% endif %}
{% if parent_label_class is defined %}
{% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' ' ~ parent_label_class)|trim}) %}
{% endif %}
{% if label is not same as(false) and label is empty %}
{% set label = name|humanize %}
{% endif %}
<label{% for attrname, attrvalue in label_attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %}>
{{- widget|raw }} {{ label is not same as(false) ? (translation_domain is same as(false) ? label|raw : label|trans({}, translation_domain)|raw ) -}}
</label>
{% endif %}
{% endblock radio_label %}
and my twig
{% autoescape %}
{{form_label(form_categories.subscription.subscriptionBilling)}}
{{form_widget(form_categories.subscription.subscriptionBilling)}}
{% endautoescape %}
and it seems to work

Symfony2 form theme override collection entry row with loop?

I have a problem with my form collection entries.
For example my form type.
// ProfileFormType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('emails', 'collection', array(
'type' => new EmailType(),
'allow_add' => true,
'allow_delete' => true
))
;
}
Thats the email type for the collection above.
// EmailType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email', 'email')
->add('publicAccess')
;
}
And now the form theme for that collection entry row.
{%- block _fos_user_profile_form_emails_entry_row %}
<div class="email form-group{% if form.email.vars.errors | length %} has-error{% endif %}">
<div class="input-group col-xs-3">
<span class="input-group-addon">{{ form_widget(form.publicAccess) }}</span>
{{ form_widget(form.email) }}
{#% if loop is defined %}{{ dump(loop) }}{% endif %#}
<span class="input-group-btn"><a class="btn btn-success"><span class="glyphicon glyphicon-plus glyphicon-xs"></span></a></span>
</div>
{{ form_errors(form.email) }}
</div>
{% endblock -%}
But the problem is that i have no loop or other things for the entries...
How can i check if the entry is the last entry of the collection?
The reason is that i want to add the "minus" button instead the "glypicon-plus" for all entries which not the last one.
You can access the last entry with :
{% if loop.last %}
{# your custom code for the last entry #}
{% else %}
{# the others entries #}
{% endif %}
So you can use it to choose the good button :
{%- block _fos_user_profile_form_emails_entry_row %}
<div class="email form-group{% if form.email.vars.errors | length %} has-error{% endif %}">
<div class="input-group col-xs-3">
<span class="input-group-addon">{{ form_widget(form.publicAccess) }}</span>
{{ form_widget(form.email) }}
<span class="input-group-btn"><a class="btn btn-success"><span class="glyphicon {{ (loop.last) ? 'glypicon-plus' : 'glyphicon-minus' }} glyphicon-xs"></span></a></span>
</div>
{{ form_errors(form.email) }}
</div>
{% endblock -%}
I think you should compare the entry with the parent's last element.
Like this:
{% if form == form.parent | last %}
it's the last
{% else %}
it's not the last
{% endif %}

Symfony2 Form Builder - Remove label, make it placeholder

I am playing with Symfony's form builder, and I can't find a way to not display a label. Further, I am interested in actually setting a placeholder for each input box. Is this possible? I have researched a bit and found nothing.
My form:
<form action="{{ path('searchPeople') }}" method="post" class="form-inline">
{{ form_errors(form) }}
{{ form_row(form.first_name) }}
{{ form_row(form.last_name) }}
{{ form_rest(form) }}
<br />
<button type="submit" class="btn btn-primary" /><i class="icon-search"></i>Search</button>
</form>
I know it's already answered, but might help somebody who is looking for a different solution for placeholders, if you don't want to change anything in your twig template:
$builder->add(
'name',
'text',
array(
'attr' => array(
'placeholder' => 'Your name',
),
'label' => false,
)
);
If you're outputting the field with form_rest you'll have to set the label for the the field to false in the form builder with something like
$builder->add('first_name', 'text', array(
'label' => false,
));
If you output the fields individually, you can omit the form_label for that field in the twig template, or set it to an empty string.
{{ form_label(form.first_name, '') }}
Convert label to placeholder
{% use 'form_div_layout.html.twig' with widget_attributes as base_widget_attributes %}
{% block widget_attributes %}
{% set attr = {'placeholder': label|trans({}, translation_domain)} %}
{{- block('base_widget_attributes') -}}
{% endblock widget_attributes %}
I did this recently! :) You'll want to create a new fields template, for form_row and one for form_widget. Then remove the form_label part, and add your placeholder.
http://symfony.com/doc/current/cookbook/form/form_customization.html
You can do it per field, or set it for all of them.
Or you can also skip the removing the form_label from the form_row template, and just do form_widget() where you're currently calling form_row()
for other that come across this label-question:
you could use form theme to override the form_row tag for every form you want. However I recommend to just set it invisible for page reader optimization. my example with bootstrap:
{% block form_row %}
{% spaceless %}
{{ form_label(form, null, {'label_attr': {'class': 'sr-only'}}) }}
{{ form_errors(form) }}
{{ form_widget(form) }}
{% endspaceless %}
{% endblock form_row %}
don't forget to include your formtheme in config.yml and template.
For those NOT using form_row, you can always add the placeholder as an attribute directly when adding the input to the builder. Like so:
$task = new Task();
$form = $this->createFormBuilder($task)
->add('first_name', 'text', array(
'required' => true,
'trim' => true,
'attr' => array('placeholder' => 'Lorem Ipsum')
)->getForm();
Symfony 2.8 & above
Remove form_label
{% block form_row %}
<div>
{{ form_errors(form) }}
{{ form_widget(form) }}
</div>
{% endblock form_row %}
Add placeholder attribute
{% block form_widget_simple %}
{% set type = type|default('text') %}
<input placeholder="{{ translation_domain is same as(false) ? label : label|trans({}, translation_domain) }}" type="{{ type }}" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ value }}" {% endif %}/>
{% endblock form_widget_simple %}
Expanding on Léo's answer:
{% use 'form_div_layout.html.twig' %}
{% block widget_attributes %}
{% spaceless %}
{% set attr = attr|merge({'placeholder': label}) %}
{{ parent() }}
{% endspaceless %}
{% endblock widget_attributes %}
trans filter has been removed because it is included in the parent.
You must render the form manually.
Here's an example:
<form id="form-message" action="{{ path('home') }}" method="post" {{ form_enctype(form) }}>
{{ form_label(form.name) }}
{% if form_errors(form.name) %}
<div class="alert alert-error">
{{ form_errors(form.name) }}
</div>
{% endif %}
{{ form_widget(form.name) }}
{{ form_row(form._token) }}
<input type="submit" class="btn" value="Submit">
</form>
Related documentation
To sums it up:
Titi's answer is the most simple ;
Mick, Léo & Quolonel's answers are the most effective but are incomplete (for symfony > 2.6) :
If you use the label_format option in your *Type::configureOptions, their solution does not work. You need to add the content of the form_label block to handle all the label possibilities.
The full & most effective answer (code used w/ symfony 3.3) :
Remove form_label
{% block form_row %}
<div>
{{ form_errors(form) }}
{{ form_widget(form) }}
</div>
{% endblock form_row %}
Edit the widget_attribute block
{% block widget_attributes %}
{% spaceless %}
{% if label is not same as(false) -%}
{% if label is empty -%}
{%- if label_format is not empty -%}
{% set label = label_format|replace({
'%name%': name,
'%id%': id,
}) %}
{%- else -%}
{% set label = name|humanize %}
{%- endif -%}
{%- endif -%}
{% set attr = attr|merge({'placeholder': label}) %}
{%- endif -%}
{{ parent() }}
{% endspaceless %}
{% endblock widget_attributes %}
Notes :
Do not translate the labels into the widget_attributes block, otherwise they will appear as missing translations.
The solution does not work for checkboxes or radio buttons, you'll want to add something like :
{%- block checkbox_widget -%}
{{ parent() }}
{{- form_label(form) -}}
{%- endblock checkbox_widget -%}
Bootstrap Forms
In my case best is mix aswers of #Cethy and #Quolonel Questions
{% form_theme form _self %}
{% use 'bootstrap_4_layout.html.twig' %}
{% block widget_attributes %} {# set placeholder #}
{% spaceless %}
{% set attr = attr|merge({'placeholder': label}) %}
{{ parent() }}
{% endspaceless %}
{% endblock widget_attributes %}
{% block form_row %} {# remove label #}
<div class="form-group">
{{ form_errors(form) }}
{{ form_widget(form) }}
</div>
{% endblock form_row %}
It looks the following
It works with translations
You can also copy the labels into the placeholder attribute before rendering the form:
$formView = $form->createView();
foreach($formView->getIterator() as $item) {
/** #var $item FormView */
if ($item->vars['label']) {
$item->vars['attr']['placeholder'] =$item->vars['label'];
}
}

Shortcuts with Symfony2 Twig forms : form_widget

I would like to replace:
{{ form_errors(form.name) }}
{{ form_widget(form.name, { 'attr': {'placeholder': 'Nom'} }) }}
By:
{{ form.name|field('Nom') }}
How could I do that? I tried to do it in a Twig extension but I don't have access to the form_widget function.
Edit: I could do it with the form.name properties (that include the parent form) but I would repeat symfony code, it would be a very ugly big hack
Makes more sense if you ask me to move the attr to your form class:
class SomeForm extends AbstractType {
//.....
$builder->add('name', 'text', array('attr' => array('placeholder'=>'Nom')));
}
Since i guess you need some custom rendering for some of your fields you can check:
http://symfony.com/doc/2.0/cookbook/form/form_customization.html#how-to-customize-an-individual-field
You could also create a new type and customize it as explained here:
http://symfony.com/doc/2.0/cookbook/form/form_customization.html#what-are-form-themes
You could even change the default way of rendering and ask symfony to render your placeholder tag by default using the field's label string (the details of enabling the form theme globally are covered by the link referenced above):
{% block text_widget %}
{% set type = type|default('text') %}
<input type="text" {{ block('widget_attributes') }} value="{{ value }}" />
{% endblock field_widget %}
{% block widget_attributes %}
{% spaceless %}
{% for attrname,attrvalue in attr %}{{attrname}}="{{attrvalue}}" {% endfor %} placeholder="{{ label|trans }}"
{% endspaceless %}
{% endblock widget_attributes %}
{% block form_row %}
{% spaceless %}
<div class="my-class">
{{ form_errors(form) }}
{{ form_widget(form) }}
</div>
{% endspaceless %}
{% endblock form_row %}
So you would limit yourself to a form_row(form.name) using the theming that symfony provides.
Symfony's aproach looks "very" DRY/DIE to me.
Hope it helps.

How to retrieve the number of errors in a subform (FormType) in Symfony2

I have a ProfileType Form in my UserBundle (extending from SonataUserBundle), in the ProfileType Form I added a sub form type (AddressType()).
ProfileType
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('gender')
->add('firstname')
->add('lastname')
->add('middlename')
->add('dateOfBirth', 'birthday', array('required' => false))
->add('phone')
->add('address', new AddressType(), array('required' => false));
;
}
/**
* {#inheritdoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => $this->class,
'validation_groups' => array('Profile', 'Address'),
'cascade_validation' => true,
));
}
AddressType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('postalcode')
->add('houseNumber')
->add('houseNumberAddition')
->add('street')
->add('city')
->add('country')
;
}
In the edit_profile_html.twig I like to know if there are any errors specifically in the AddressType. But form.address.vars.errors does not return the number of invalid elements in AddressType. If I specifically check the errors for a element in AddressType it does return the correct count.
So: form.address.vars.errors.length is 0, but form.address.postalcode.var.errors.length work. But I do not want to check for all elements one by one.
<fieldset {% if form.address.vars.errors|length>0 %} class="warning" {% endif %} >
<legend>{% trans %}vg.userbundle.form.address.legend{% endtrans %}</legend>
{{ form_rest(form.address) }}
</fieldset>
edit_profile_html.twig
{% block subtitle %}{{ "title_user_account" | trans({}, 'SonataUserBundle') }} - {{ "title_user_edit_profile" | trans({}, 'SonataUserBundle') }}{% endblock %}
{% block content %}
{% block fos_user_content %}
<form novalidate action="{{ path('sonata_user_profile_edit') }}" method="POST">
<fieldset {% if form.vars.errors|length>0 %} class="warning" {% endif %} >
<legend>{% trans %}vg.userbundle.form.profile.legend{% endtrans %}</legend>
{{ form_row(form.gender) }}
{{ form_row(form.firstname) }}
{{ form_row(form.lastname) }}
{{ form_row(form.middlename) }}
{{ form_row(form.dateOfBirth) }}
{{ form_row(form.phone) }}
</fieldset>
<fieldset {% if form.address.vars.errors|length>0 %} class="warning" {% endif %} >
<legend>{% trans %}vg.userbundle.form.address.legend{% endtrans %}</legend>
{{ form_rest(form.address) }}
</fieldset>
<fieldset class="submit">
<ul>
<li><input type="submit" name="submit"
value="{{ 'sonata_user_submit'|trans({}, 'SonataUserBundle') }}"/></li>
</ul>
</fieldset>
</form>
{% endblock %}
{% endblock content %}
So what is the correct way to retrieve the number of invalid elements of an 'embedded' FormType?
You can check if a form and all of its elements are valid (=have no errors) using the "valid" variable (which I guess is what you want to do):
<fieldset {% if not form.address.vars.valid %} class="warning" {% endif %} >
I suppose there is no way round to iterate through the object address and count the errors of child items.
{% for child in address %}
{{ ...count errors... }}
{% endfor %}
Note that every form item is an object which means that every child of "address" is an object as well.

Categories