feature #30267 [Form] add option to render NumberType as type="number" (xabbuh)

This PR was merged into the 4.3-dev branch.

Discussion
----------

[Form] add option to render NumberType as type="number"

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #8106
| License       | MIT
| Doc PR        | symfony/symfony-docs#10997

Commits
-------

42e8f5e3a2 add option to render NumberType as type="number"
This commit is contained in:
Fabien Potencier 2019-02-21 09:19:09 +01:00
commit 6207f19317
7 changed files with 89 additions and 4 deletions

View File

@ -170,7 +170,7 @@
{%- endblock dateinterval_widget -%} {%- endblock dateinterval_widget -%}
{%- block number_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') -%} {%- set type = type|default('text') -%}
{{ block('form_widget_simple') }} {{ block('form_widget_simple') }}
{%- endblock number_widget -%} {%- endblock number_widget -%}

View File

@ -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() public function testPassword()
{ {
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\PasswordType', 'foo&bar'); $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\PasswordType', 'foo&bar');

View File

@ -8,6 +8,7 @@ CHANGELOG
exception in 5.0. exception in 5.0.
* Using names for buttons that do not contain only letters, digits, underscores, hyphens, and colons is deprecated and * 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. 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` * deprecated using the `date_format`, `date_widget`, and `time_widget` options of the `DateTimeType` when the `widget`
option is set to `single_text` option is set to `single_text`
* added `block_prefix` option to `BaseType`. * added `block_prefix` option to `BaseType`.

View File

@ -77,8 +77,9 @@ class NumberToLocalizedStringTransformer implements DataTransformerInterface
protected $roundingMode; protected $roundingMode;
private $scale; 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) { if (null === $grouping) {
$grouping = false; $grouping = false;
@ -91,6 +92,7 @@ class NumberToLocalizedStringTransformer implements DataTransformerInterface
$this->scale = $scale; $this->scale = $scale;
$this->grouping = $grouping; $this->grouping = $grouping;
$this->roundingMode = $roundingMode; $this->roundingMode = $roundingMode;
$this->locale = $locale;
} }
/** /**
@ -214,7 +216,7 @@ class NumberToLocalizedStringTransformer implements DataTransformerInterface
*/ */
protected function getNumberFormatter() protected function getNumberFormatter()
{ {
$formatter = new \NumberFormatter(\Locale::getDefault(), \NumberFormatter::DECIMAL); $formatter = new \NumberFormatter($this->locale ?? \Locale::getDefault(), \NumberFormatter::DECIMAL);
if (null !== $this->scale) { if (null !== $this->scale) {
$formatter->setAttribute(\NumberFormatter::FRACTION_DIGITS, $this->scale); $formatter->setAttribute(\NumberFormatter::FRACTION_DIGITS, $this->scale);

View File

@ -12,8 +12,12 @@
namespace Symfony\Component\Form\Extension\Core\Type; namespace Symfony\Component\Form\Extension\Core\Type;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Exception\LogicException;
use Symfony\Component\Form\Extension\Core\DataTransformer\NumberToLocalizedStringTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\NumberToLocalizedStringTransformer;
use Symfony\Component\Form\FormBuilderInterface; 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; use Symfony\Component\OptionsResolver\OptionsResolver;
class NumberType extends AbstractType class NumberType extends AbstractType
@ -26,10 +30,21 @@ class NumberType extends AbstractType
$builder->addViewTransformer(new NumberToLocalizedStringTransformer( $builder->addViewTransformer(new NumberToLocalizedStringTransformer(
$options['scale'], $options['scale'],
$options['grouping'], $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} * {@inheritdoc}
*/ */
@ -41,6 +56,7 @@ class NumberType extends AbstractType
'grouping' => false, 'grouping' => false,
'rounding_mode' => NumberToLocalizedStringTransformer::ROUND_HALF_UP, 'rounding_mode' => NumberToLocalizedStringTransformer::ROUND_HALF_UP,
'compound' => false, 'compound' => false,
'html5' => false,
]); ]);
$resolver->setAllowedValues('rounding_mode', [ $resolver->setAllowedValues('rounding_mode', [
@ -54,6 +70,15 @@ class NumberType extends AbstractType
]); ]);
$resolver->setAllowedTypes('scale', ['null', 'int']); $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;
});
} }
/** /**

View File

@ -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() public function testPassword()
{ {
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\PasswordType', 'foo&bar'); $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\PasswordType', 'foo&bar');

View File

@ -75,4 +75,28 @@ class NumberTypeTest extends BaseTypeTest
$this->assertSame($expectedData, $form->getNormData()); $this->assertSame($expectedData, $form->getNormData());
$this->assertSame($expectedData, $form->getData()); $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,
]);
}
} }