diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig
index 0fb3f769b8391..6a610d04b12d8 100644
--- a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig
+++ b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig
@@ -232,6 +232,18 @@
{%- endblock radio_row %}
+{# Support #}
+
+{% block empty_row -%}
+ {%- set translation_domain = translation_domain ?: 'sf_form' -%}
+ {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-control')|trim}) %}
+
+
+ {{- translation_domain is same as(false) ? empty_view : empty_view|trans({}, translation_domain) -}}
+
+
+{%- endblock empty_row %}
+
{# Errors #}
{% block form_errors -%}
diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig
index 63104def7d3eb..65319a00c1183 100644
--- a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig
+++ b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig
@@ -2,7 +2,12 @@
{%- block form_widget -%}
{% if compound %}
- {{- block('form_widget_compound') -}}
+ {%- if empty_view %}
+ {{- block('empty_row') -}}
+ {{ form_rest(form) }}
+ {%- else -%}
+ {{- block('form_widget_compound') -}}
+ {%- endif -%}
{% else %}
{{- block('form_widget_simple') -}}
{% endif %}
@@ -35,11 +40,15 @@
{%- endblock textarea_widget -%}
{%- block choice_widget -%}
- {% if expanded %}
- {{- block('choice_widget_expanded') -}}
- {% else %}
- {{- block('choice_widget_collapsed') -}}
- {% endif %}
+ {%- if empty_view is defined and empty_view -%}
+ {{- block('empty_row') -}}
+ {%- else -%}
+ {%- if expanded -%}
+ {{- block('choice_widget_expanded') -}}
+ {%- else -%}
+ {{- block('choice_widget_collapsed') -}}
+ {%- endif -%}
+ {%- endif -%}
{%- endblock choice_widget -%}
{%- block choice_widget_expanded -%}
@@ -313,6 +322,13 @@
{% endfor %}
{%- endblock form_rows -%}
+{%- block empty_row -%}
+ {%- set translation_domain = translation_domain ?: 'sf_form' -%}
+
+ {{- translation_domain is same as(false) ? empty_view : empty_view|trans({}, translation_domain) -}}
+
+{%- endblock empty_row -%}
+
{%- block widget_attributes -%}
id="{{ id }}" name="{{ full_name }}"
{%- if disabled %} disabled="disabled"{% endif -%}
diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/form_table_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/form_table_layout.html.twig
index c7b3a4365b51b..5a676977fc331 100644
--- a/src/Symfony/Bridge/Twig/Resources/views/Form/form_table_layout.html.twig
+++ b/src/Symfony/Bridge/Twig/Resources/views/Form/form_table_layout.html.twig
@@ -42,3 +42,14 @@
{{- form_rest(form) -}}
{%- endblock form_widget_compound -%}
+
+{%- block empty_row -%}
+ {%- set translation_domain = translation_domain ?: 'sf_form' -%}
+
+
+
+ {{ translation_domain is same as(false) ? empty_view : empty_view|trans({}, translation_domain) }}
+ |
+
+
+{%- endblock empty_row -%}
diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/foundation_5_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/foundation_5_layout.html.twig
index dc7bec9fb6ccd..2e739f0f79cb0 100644
--- a/src/Symfony/Bridge/Twig/Resources/views/Form/foundation_5_layout.html.twig
+++ b/src/Symfony/Bridge/Twig/Resources/views/Form/foundation_5_layout.html.twig
@@ -184,6 +184,19 @@
{% endif %}
{%- endblock choice_widget_expanded %}
+{% block choice_widget_empty -%}
+ {%- if empty_choices is empty %}
+ {%- set empty_choices = 'No choice available' %}
+ {%- set choice_translation_domain = 'sf_form' %}
+ {%- endif -%}
+ {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-control')|trim}) %}
+
+
+ {{- choice_translation_domain is same as(false) ? empty_choices : empty_choices|trans({}, choice_translation_domain) -}}
+
+
+{%- endblock %}
+
{% block checkbox_widget -%}
{% set parent_label_class = parent_label_class|default('') %}
{% if errors|length > 0 -%}
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_widget.html.php
index 13593a96f11ef..a5e6c410dba98 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_widget.html.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_widget.html.php
@@ -1,5 +1,9 @@
-
-block($form, 'choice_widget_expanded') ?>
+
+ block($form, 'empty_row')?>
-block($form, 'choice_widget_collapsed') ?>
+
+ block($form, 'choice_widget_expanded') ?>
+
+ block($form, 'choice_widget_collapsed') ?>
+
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/empty_row.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/empty_row.html.php
new file mode 100644
index 0000000000000..a794ca2204888
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/empty_row.html.php
@@ -0,0 +1,9 @@
+
+block($form, 'widget_container_attributes') ?>>
+
+ escape(false !== $translation_domain
+ ? $view['translator']->trans($empty_view, array(), $translation_domain)
+ : $empty_view
+ ); ?>
+
+
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_widget.html.php
index c5af39a5b6b06..479f6466ba645 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_widget.html.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_widget.html.php
@@ -1,5 +1,10 @@
-block($form, 'form_widget_compound')?>
+
+ block($form, 'empty_row')?>
+ rest($form) ?>
+
+ block($form, 'form_widget_compound')?>
+
block($form, 'form_widget_simple')?>
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/empty_row.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/empty_row.html.php
new file mode 100644
index 0000000000000..05697705196de
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/empty_row.html.php
@@ -0,0 +1,13 @@
+
+block($form, 'widget_container_attributes') ?>>
+
+
+
+ escape(false !== $translation_domain
+ ? $view['translator']->trans($empty_view, array(), $translation_domain)
+ : $empty_view
+ ); ?>
+
+ |
+
+
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php
index ca1e5b737b9ab..8302b128f0583 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php
@@ -244,6 +244,18 @@ public function finishView(FormView $view, FormInterface $form, array $options)
$childView->vars['full_name'] = $childName;
}
}
+
+ // We have to override parent {@link FormType} condition of the empty_view option here,
+ // even if there is no choices the field view may not be empty because:
+ //
+ // - a select input is not "compound" but should be considered as any empty HTML tag,
+ //
+ // - when expanded it could hold a placeholder which has been counted as a children,
+ // we should ignore it in any case since its role is to be a "null" alternative to
+ // choices, not a choice itself. Think of "Choose something" with no choices.
+ if (array() === $view->vars['choices'] && array() === $view->vars['preferred_choices']) {
+ $view->vars['empty_view'] = $options['empty_view'];
+ }
}
/**
@@ -323,6 +335,7 @@ public function configureOptions(OptionsResolver $resolver)
'preferred_choices' => array(),
'group_by' => null,
'empty_data' => $emptyData,
+ 'empty_view' => 'No choice available',
'placeholder' => $placeholderDefault,
'error_bubbling' => false,
'compound' => $compound,
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php
index ff8d0b4fdecf7..89f21037f2ce9 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php
@@ -113,6 +113,10 @@ public function finishView(FormView $view, FormInterface $form, array $options)
}
$view->vars['multipart'] = $multipart;
+
+ if ($form->getConfig()->getCompound()) {
+ $view->vars['empty_view'] = (bool) $view->count() ? false : $options['empty_view'];
+ }
}
/**
@@ -142,6 +146,12 @@ public function configureOptions(OptionsResolver $resolver)
};
};
+ // For any compound form which has no children, show some text
+ // instead of an empty HTML tag by default
+ $emptyView = function (Options $options) {
+ return $options['compound'] ? 'No fields' : false;
+ };
+
// For any form that is not represented by a single HTML control,
// errors should bubble up by default
$errorBubbling = function (Options $options) {
@@ -157,6 +167,7 @@ public function configureOptions(OptionsResolver $resolver)
$resolver->setDefaults(array(
'data_class' => $dataClass,
'empty_data' => $emptyData,
+ 'empty_view' => $emptyView,
'trim' => true,
'required' => true,
'property_path' => null,
@@ -175,6 +186,7 @@ public function configureOptions(OptionsResolver $resolver)
));
$resolver->setAllowedTypes('label_attr', 'array');
+ $resolver->setAllowedTypes('empty_view', array('null', 'string', 'bool'));
}
/**
diff --git a/src/Symfony/Component/Form/Resources/translations/sf_form.en.xlf b/src/Symfony/Component/Form/Resources/translations/sf_form.en.xlf
new file mode 100644
index 0000000000000..ac2d89b83510c
--- /dev/null
+++ b/src/Symfony/Component/Form/Resources/translations/sf_form.en.xlf
@@ -0,0 +1,11 @@
+
+
+
+
+
+ No choice available
+ No choice available
+
+
+
+
diff --git a/src/Symfony/Component/Form/Resources/translations/sf_form.fr.xlf b/src/Symfony/Component/Form/Resources/translations/sf_form.fr.xlf
new file mode 100644
index 0000000000000..8fa4757746db8
--- /dev/null
+++ b/src/Symfony/Component/Form/Resources/translations/sf_form.fr.xlf
@@ -0,0 +1,11 @@
+
+
+
+
+
+ No choice available
+ Aucun choix disponible
+
+
+
+
diff --git a/src/Symfony/Component/Form/Tests/AbstractBootstrap3LayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractBootstrap3LayoutTest.php
index 2dbf871aba902..bb7606c6e0a09 100644
--- a/src/Symfony/Component/Form/Tests/AbstractBootstrap3LayoutTest.php
+++ b/src/Symfony/Component/Form/Tests/AbstractBootstrap3LayoutTest.php
@@ -250,6 +250,27 @@ public function testSelectWithSizeBiggerThanOneCanBeRequired()
);
}
+ public function testSingleChoiceWithoutChoices()
+ {
+ $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array(
+ 'choices' => array(),
+ 'multiple' => false,
+ 'expanded' => false,
+ 'required' => true,
+ 'attr' => array('class' => 'my&class'),
+ ));
+
+ $this->assertMatchesXpath($this->renderWidget($form->createView()),
+'/div
+ [@class="my&class form-control"]
+ [not(@required)]
+ [
+ ./p/em/.="[trans]No choice available[/trans]"
+ ]
+'
+ );
+ }
+
public function testSingleChoiceWithoutTranslation()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array(
@@ -813,6 +834,27 @@ public function testSingleChoiceExpandedWithLabelsSetFalseByCallable()
);
}
+ public function testSingleChoiceExpandedWithoutChoices()
+ {
+ $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array(
+ 'choices' => array(),
+ 'multiple' => false,
+ 'expanded' => true,
+ 'required' => true,
+ 'attr' => array('class' => 'my&class'),
+ ));
+
+ $this->assertMatchesXpath($this->renderWidget($form->createView()),
+'/div
+ [@class="my&class form-control"]
+ [not(@required)]
+ [
+ ./p/em/.="[trans]No choice available[/trans]"
+ ]
+'
+ );
+ }
+
public function testSingleChoiceExpandedWithoutTranslation()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array(
diff --git a/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php
index 44e2f1d72dee2..9d6fe0fe1ae35 100644
--- a/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php
+++ b/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php
@@ -327,10 +327,15 @@ public function testEmptyCollection()
'entry_type' => 'Symfony\Component\Form\Extension\Core\Type\TextType',
));
- $this->assertWidgetMatchesXpath($form->createView(), array(),
+ $this->assertWidgetMatchesXpath($form->createView(), array('id' => 'my&id', 'attr' => array('class' => 'my&class')),
'/div
- [./input[@type="hidden"][@id="names__token"]]
+ [@id="my&id"]
+ [@class="my&class"]
+ [
+ /em[.="[trans]No fields[/trans]"]
+ ]
[count(./div)=0]
+/following-sibling::input[@type="hidden"][@id="names__token"]
'
);
}
diff --git a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php
index 4ff4f5407b8cc..de0799a1ed63b 100644
--- a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php
+++ b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php
@@ -536,6 +536,23 @@ public function testSelectWithSizeBiggerThanOneCanBeRequired()
);
}
+ public function testSingleChoiceWithoutChoices()
+ {
+ $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array(
+ 'choices' => array(),
+ 'multiple' => false,
+ 'expanded' => false,
+ 'required' => true,
+ ));
+
+ $this->assertWidgetMatchesXpath($form->createView(), array(),
+'/div
+ [not(@required)]
+ /em[.="[trans]No choice available[/trans]"]
+'
+ );
+ }
+
public function testSingleChoiceWithoutTranslation()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array(
@@ -955,6 +972,23 @@ public function testSingleChoiceExpanded()
);
}
+ public function testSingleChoiceExpandedWithoutChoices()
+ {
+ $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array(
+ 'choices' => array(),
+ 'multiple' => false,
+ 'expanded' => true,
+ 'required' => true,
+ ));
+
+ $this->assertWidgetMatchesXpath($form->createView(), array(),
+'/div
+ [not(@required)]
+ /em[.="[trans]No choice available[/trans]"]
+'
+ );
+ }
+
public function testSingleChoiceExpandedWithoutTranslation()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array(
diff --git a/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php
index 9902a6dc089f6..61749d71892b8 100644
--- a/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php
+++ b/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php
@@ -216,10 +216,56 @@ public function testEmptyCollection()
'entry_type' => 'Symfony\Component\Form\Extension\Core\Type\TextType',
));
+ $this->assertWidgetMatchesXpath($form->createView(), array('id' => 'my&id', 'attr' => array('class' => 'my&class')),
+'/table
+ [
+ ./tr
+ [
+ ./td[@colspan="2"]/em[.="[trans]No fields[/trans]"]
+ ]
+ ]
+ [@id="my&id"]
+ [@class="my&class"]
+/following-sibling::tr[@style="display: none"]
+ [
+ ./td[@colspan="2"]/input[@type="hidden"][@id="names__token"]
+ ]
+'
+ );
+ }
+
+ public function testSingleChoiceWithoutChoices()
+ {
+ $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array(
+ 'choices' => array(),
+ 'multiple' => false,
+ 'expanded' => false,
+ 'required' => true,
+ ));
+
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/table
- [./tr[@style="display: none"][./td[@colspan="2"]/input[@type="hidden"][@id="names__token"]]]
- [count(./tr[./td/input])=1]
+ [
+ ./tr/td[@colspan="2"]/em[.="[trans]No choice available[/trans]"]
+ ]
+'
+ );
+ }
+
+ public function testSingleChoiceExpandedWithoutChoices()
+ {
+ $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array(
+ 'choices' => array(),
+ 'multiple' => false,
+ 'expanded' => true,
+ 'required' => true,
+ ));
+
+ $this->assertWidgetMatchesXpath($form->createView(), array(),
+'/table
+ [
+ ./tr/td[@colspan="2"]/em[.="[trans]No choice available[/trans]"]
+ ]
'
);
}