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 234faab533..52a639a333 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 @@ -170,7 +170,7 @@ {%- endblock dateinterval_widget -%} {%- block number_widget -%} - {# type="number" doesn't work with floats #} + {# type="number" doesn't work with floats in localized formats #} {%- set type = type|default('text') -%} {{ block('form_widget_simple') }} {%- endblock number_widget -%} diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTest.php index 160c18cce3..254f9a4d1f 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTest.php @@ -2089,6 +2089,22 @@ abstract class AbstractBootstrap3LayoutTest extends AbstractLayoutTest ); } + public function testRenderNumberWithHtml5NumberType() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\NumberType', 1234.56, [ + 'html5' => true, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], + '/input + [@type="number"] + [@name="name"] + [@class="my&class form-control"] + [@value="1234.56"] +' + ); + } + public function testPassword() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\PasswordType', 'foo&bar'); diff --git a/src/Symfony/Component/Form/CHANGELOG.md b/src/Symfony/Component/Form/CHANGELOG.md index 2304727571..82f6a662c7 100644 --- a/src/Symfony/Component/Form/CHANGELOG.md +++ b/src/Symfony/Component/Form/CHANGELOG.md @@ -8,6 +8,7 @@ CHANGELOG exception in 5.0. * Using names for buttons that do not contain only letters, digits, underscores, hyphens, and colons is deprecated and will lead to an exception in 5.0. + * added `html5` option to `NumberType` that allows to render `type="number"` input fields * deprecated using the `date_format`, `date_widget`, and `time_widget` options of the `DateTimeType` when the `widget` option is set to `single_text` * added `block_prefix` option to `BaseType`. diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php index b019fff4a8..a93a9bf246 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php @@ -77,8 +77,9 @@ class NumberToLocalizedStringTransformer implements DataTransformerInterface protected $roundingMode; private $scale; + private $locale; - public function __construct(int $scale = null, ?bool $grouping = false, ?int $roundingMode = self::ROUND_HALF_UP) + public function __construct(int $scale = null, ?bool $grouping = false, ?int $roundingMode = self::ROUND_HALF_UP, string $locale = null) { if (null === $grouping) { $grouping = false; @@ -91,6 +92,7 @@ class NumberToLocalizedStringTransformer implements DataTransformerInterface $this->scale = $scale; $this->grouping = $grouping; $this->roundingMode = $roundingMode; + $this->locale = $locale; } /** @@ -214,7 +216,7 @@ class NumberToLocalizedStringTransformer implements DataTransformerInterface */ protected function getNumberFormatter() { - $formatter = new \NumberFormatter(\Locale::getDefault(), \NumberFormatter::DECIMAL); + $formatter = new \NumberFormatter($this->locale ?? \Locale::getDefault(), \NumberFormatter::DECIMAL); if (null !== $this->scale) { $formatter->setAttribute(\NumberFormatter::FRACTION_DIGITS, $this->scale); diff --git a/src/Symfony/Component/Form/Extension/Core/Type/NumberType.php b/src/Symfony/Component/Form/Extension/Core/Type/NumberType.php index 1054b5899b..a0257f0269 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/NumberType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/NumberType.php @@ -12,8 +12,12 @@ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Exception\LogicException; use Symfony\Component\Form\Extension\Core\DataTransformer\NumberToLocalizedStringTransformer; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; class NumberType extends AbstractType @@ -26,10 +30,21 @@ class NumberType extends AbstractType $builder->addViewTransformer(new NumberToLocalizedStringTransformer( $options['scale'], $options['grouping'], - $options['rounding_mode'] + $options['rounding_mode'], + $options['html5'] ? 'en' : null )); } + /** + * {@inheritdoc} + */ + public function buildView(FormView $view, FormInterface $form, array $options) + { + if ($options['html5']) { + $view->vars['type'] = 'number'; + } + } + /** * {@inheritdoc} */ @@ -41,6 +56,7 @@ class NumberType extends AbstractType 'grouping' => false, 'rounding_mode' => NumberToLocalizedStringTransformer::ROUND_HALF_UP, 'compound' => false, + 'html5' => false, ]); $resolver->setAllowedValues('rounding_mode', [ @@ -54,6 +70,15 @@ class NumberType extends AbstractType ]); $resolver->setAllowedTypes('scale', ['null', 'int']); + $resolver->setAllowedTypes('html5', 'bool'); + + $resolver->setNormalizer('grouping', function (Options $options, $value) { + if (true === $value && $options['html5']) { + throw new LogicException('Cannot use the "grouping" option when the "html5" option is enabled.'); + } + + return $value; + }); } /** diff --git a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php index b5f0b8f84e..5b4b84e9e5 100644 --- a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php @@ -1871,6 +1871,23 @@ abstract class AbstractLayoutTest extends FormIntegrationTestCase ); } + public function testRenderNumberWithHtml5NumberType() + { + $this->requiresFeatureSet(403); + + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\NumberType', 1234.56, [ + 'html5' => true, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), [], +'/input + [@type="number"] + [@name="name"] + [@value="1234.56"] +' + ); + } + public function testPassword() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\PasswordType', 'foo&bar'); diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/NumberTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/NumberTypeTest.php index c19c82b117..1ab6c8e9fb 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/NumberTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/NumberTypeTest.php @@ -75,4 +75,28 @@ class NumberTypeTest extends BaseTypeTest $this->assertSame($expectedData, $form->getNormData()); $this->assertSame($expectedData, $form->getData()); } + + public function testIgnoresDefaultLocaleToRenderHtml5NumberWidgets() + { + $form = $this->factory->create(static::TESTED_TYPE, null, [ + 'scale' => 2, + 'rounding_mode' => \NumberFormatter::ROUND_UP, + 'html5' => true, + ]); + $form->setData(12345.54321); + + $this->assertSame('12345.55', $form->createView()->vars['value']); + $this->assertSame('12345.55', $form->getViewData()); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\LogicException + */ + public function testGroupingNotAllowedWithHtml5Widget() + { + $this->factory->create(static::TESTED_TYPE, null, [ + 'grouping' => true, + 'html5' => true, + ]); + } }