From 7d46015cd718d1f0dad8a1597d72090d96afbd4b Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Thu, 3 Oct 2024 14:53:03 +0300 Subject: [PATCH 1/6] start --- src/Field/Checkbox.php | 25 ++++++++++++++++++++++--- src/Field/CheckboxLabelPlacement.php | 12 ++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 src/Field/CheckboxLabelPlacement.php diff --git a/src/Field/Checkbox.php b/src/Field/Checkbox.php index 46f5ceef..f0a3f27c 100644 --- a/src/Field/Checkbox.php +++ b/src/Field/Checkbox.php @@ -26,6 +26,7 @@ final class Checkbox extends InputField implements ValidationClassInterface private ?string $uncheckValue = '0'; private bool $enclosedByLabel = true; + private CheckboxLabelPlacement $labelPlacement = CheckboxLabelPlacement::WRAP; private ?string $inputLabel = null; private array $inputLabelAttributes = []; private bool $inputLabelEncode = true; @@ -82,6 +83,8 @@ public function disabled(bool $disabled = true): self * If the input should be enclosed by label. * * @param bool $value If the input should be en closed by label. + * + * @deprecated Use {@see labelPLacement()} instead it. */ public function enclosedByLabel(bool $value): self { @@ -90,6 +93,13 @@ public function enclosedByLabel(bool $value): self return $new; } + public function labelPlacement(CheckboxLabelPlacement $placement): self + { + $new = clone $this; + $new->labelPlacement = $placement; + return $new; + } + /** * Label displayed next to the checkbox. * @@ -233,11 +243,20 @@ protected function generateInput(): string $checkbox = Html::checkbox($this->getName(), $inputValue, $inputAttributes); - if ($this->enclosedByLabel) { + $labelPlacement = $this->enclosedByLabel + ? $this->labelPlacement + : CheckboxLabelPlacement::DEFAULT; + + if ($labelPlacement === CheckboxLabelPlacement::WRAP) { $label = $this->inputLabel ?? $this->label ?? $this->getInputData()->getLabel(); $checkbox = $checkbox ->label($label, $this->inputLabelAttributes) ->labelEncode($this->inputLabelEncode); + } elseif ($labelPlacement === CheckboxLabelPlacement::SIDE) { + $label = $this->inputLabel ?? $this->label ?? $this->getInputData()->getLabel(); + $checkbox = $checkbox + ->sideLabel($label, $this->inputLabelAttributes) + ->labelEncode($this->inputLabelEncode); } $html = $checkbox @@ -245,7 +264,7 @@ protected function generateInput(): string ->uncheckValue($this->uncheckValue) ->render(); - if (!$this->enclosedByLabel && $this->inputLabel !== null) { + if ($labelPlacement === CheckboxLabelPlacement::DEFAULT && $this->inputLabel !== null) { $html .= ' ' . ($this->inputLabelEncode ? Html::encode($this->inputLabel) : $this->inputLabel); } @@ -254,7 +273,7 @@ protected function generateInput(): string protected function shouldHideLabel(): bool { - return $this->enclosedByLabel; + return $this->labelPlacement !== CheckboxLabelPlacement::DEFAULT && $this->enclosedByLabel; } private function prepareCheckboxValue(mixed $value): ?string diff --git a/src/Field/CheckboxLabelPlacement.php b/src/Field/CheckboxLabelPlacement.php new file mode 100644 index 00000000..c4975a90 --- /dev/null +++ b/src/Field/CheckboxLabelPlacement.php @@ -0,0 +1,12 @@ + Date: Thu, 3 Oct 2024 15:11:29 +0300 Subject: [PATCH 2/6] improve --- src/Field/Checkbox.php | 29 +++++++++++++++++++--------- src/Field/CheckboxLabelPlacement.php | 14 ++++++++++++++ 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/src/Field/Checkbox.php b/src/Field/Checkbox.php index f0a3f27c..1824bce7 100644 --- a/src/Field/Checkbox.php +++ b/src/Field/Checkbox.php @@ -93,6 +93,11 @@ public function enclosedByLabel(bool $value): self return $new; } + /** + * Set label placement relative to checkbox input. + * + * @see CheckboxLabelPlacement + */ public function labelPlacement(CheckboxLabelPlacement $placement): self { $new = clone $this; @@ -103,8 +108,6 @@ public function labelPlacement(CheckboxLabelPlacement $placement): self /** * Label displayed next to the checkbox. * - * When this option is specified, the checkbox will be enclosed by a label tag. - * * @link https://www.w3.org/TR/html52/sec-forms.html#the-label-element */ public function inputLabel(?string $value): self @@ -134,7 +137,7 @@ public function addInputLabelAttributes(array $attributes): self } /** - * Set enclosed label tag ID. + * Set checkbox label tag ID. * * @param string|null $id Label tag ID. */ @@ -146,7 +149,7 @@ public function inputLabelId(?string $id): self } /** - * Replace enclosed label tag CSS classes with a new set of classes. + * Replace checkbox label tag CSS classes with a new set of classes. * * @param string|null ...$class One or many CSS classes. */ @@ -158,7 +161,7 @@ public function inputLabelClass(?string ...$class): self } /** - * Add one or more CSS classes to the enclosed label tag. + * Add one or more CSS classes to the checkbox label tag. * * @param string|null ...$class One or many CSS classes. */ @@ -243,9 +246,7 @@ protected function generateInput(): string $checkbox = Html::checkbox($this->getName(), $inputValue, $inputAttributes); - $labelPlacement = $this->enclosedByLabel - ? $this->labelPlacement - : CheckboxLabelPlacement::DEFAULT; + $labelPlacement = $this->getLabelPlacement(); if ($labelPlacement === CheckboxLabelPlacement::WRAP) { $label = $this->inputLabel ?? $this->label ?? $this->getInputData()->getLabel(); @@ -273,7 +274,7 @@ protected function generateInput(): string protected function shouldHideLabel(): bool { - return $this->labelPlacement !== CheckboxLabelPlacement::DEFAULT && $this->enclosedByLabel; + return $this->getLabelPlacement() !== CheckboxLabelPlacement::DEFAULT; } private function prepareCheckboxValue(mixed $value): ?string @@ -306,4 +307,14 @@ protected function prepareInputAttributes(array &$attributes): void $this->hasCustomError() ? true : null, ); } + + private function getLabelPlacement(): CheckboxLabelPlacement + { + // If default value, use deprecated `enclosedByLabel` property + if ($this->labelPlacement === CheckboxLabelPlacement::WRAP) { + return $this->enclosedByLabel ? CheckboxLabelPlacement::WRAP : CheckboxLabelPlacement::DEFAULT; + } + + return $this->labelPlacement; + } } diff --git a/src/Field/CheckboxLabelPlacement.php b/src/Field/CheckboxLabelPlacement.php index c4975a90..01b3a98e 100644 --- a/src/Field/CheckboxLabelPlacement.php +++ b/src/Field/CheckboxLabelPlacement.php @@ -4,9 +4,23 @@ namespace Yiisoft\Form\Field; +/** + * Placement of label in {@see Checkbox}. + */ enum CheckboxLabelPlacement { + /** + * Output label according to the template. + */ case DEFAULT; + + /** + * Wrap checkbox into label tag + */ case WRAP; + + /** + * Output label side on side of checkbox. + */ case SIDE; } From ac37cca18518c2de3e12ff8c39a287015f93844a Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Thu, 3 Oct 2024 15:13:52 +0300 Subject: [PATCH 3/6] changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d3ed31e..9a568781 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Yii Form Change Log -## 1.1.1 under development +## 1.2.0 under development -- no changes in this release. +- New #364: Add `Checkbox::labelPlacement()` method and mark `Checkbox::enclosedByLabel()` as deprecated (@vjik) ## 1.1.0 September 26, 2024 From de01983bf13d023ff3bfd7b0b7fb2950a19e59db Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Thu, 3 Oct 2024 15:20:15 +0300 Subject: [PATCH 4/6] tests --- tests/Field/CheckboxTest.php | 46 ++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/Field/CheckboxTest.php b/tests/Field/CheckboxTest.php index 3b6f76f8..81e1493d 100644 --- a/tests/Field/CheckboxTest.php +++ b/tests/Field/CheckboxTest.php @@ -9,6 +9,7 @@ use PHPUnit\Framework\TestCase; use stdClass; use Yiisoft\Form\Field\Checkbox; +use Yiisoft\Form\Field\CheckboxLabelPlacement; use Yiisoft\Form\PureField\InputData; use Yiisoft\Form\Tests\Support\StringableObject; use Yiisoft\Form\Theme\ThemeContainer; @@ -657,12 +658,57 @@ public function testInvalidClassesWithCustomError(): void $this->assertSame($expected, $result); } + public static function dataLabelPlacement(): iterable + { + yield 'default' => [ + << + + + + HTML, + CheckboxLabelPlacement::DEFAULT, + ]; + yield 'wrap' => [ + << + + + HTML, + CheckboxLabelPlacement::WRAP, + ]; + yield 'side' => [ + << + + + HTML, + CheckboxLabelPlacement::SIDE, + ]; + } + + #[DataProvider('dataLabelPlacement')] + public function testLabelPlacement(string $expected, CheckboxLabelPlacement $placement): void + { + $inputData = new InputData('city', label: 'Voronezh'); + + $result = Checkbox::widget() + ->inputData($inputData) + ->inputId('UID') + ->uncheckValue(null) + ->labelPlacement($placement) + ->render(); + + $this->assertSame($expected, $result); + } + public function testImmutability(): void { $widget = Checkbox::widget(); $this->assertNotSame($widget, $widget->uncheckValue(null)); $this->assertNotSame($widget, $widget->enclosedByLabel(true)); + $this->assertNotSame($widget, $widget->labelPlacement(CheckboxLabelPlacement::DEFAULT)); $this->assertNotSame($widget, $widget->inputLabel(null)); $this->assertNotSame($widget, $widget->inputLabelAttributes([])); $this->assertNotSame($widget, $widget->addInputLabelAttributes([])); From 56d1d69f12ffa8846ac191c7f2da045e89769356 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Thu, 3 Oct 2024 15:21:24 +0300 Subject: [PATCH 5/6] fix themes --- config/theme-bootstrap5-horizontal.php | 5 +++-- config/theme-bootstrap5-vertical.php | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/config/theme-bootstrap5-horizontal.php b/config/theme-bootstrap5-horizontal.php index 6696f6ea..6a0de46a 100644 --- a/config/theme-bootstrap5-horizontal.php +++ b/config/theme-bootstrap5-horizontal.php @@ -5,6 +5,7 @@ use Yiisoft\Form\Field\Button; use Yiisoft\Form\Field\ButtonGroup; use Yiisoft\Form\Field\Checkbox; +use Yiisoft\Form\Field\CheckboxLabelPlacement; use Yiisoft\Form\Field\CheckboxList; use Yiisoft\Form\Field\ErrorSummary; use Yiisoft\Form\Field\RadioList; @@ -24,8 +25,8 @@ 'inputInvalidClass' => 'is-invalid', 'fieldConfigs' => [ Checkbox::class => [ - 'inputContainerTag()' => ['div'], - 'addInputContainerClass()' => ['form-check'], + 'labelPlacement()' => [CheckboxLabelPlacement::SIDE], + 'addContainerClass()' => ['form-check'], 'inputClass()' => ['form-check-input'], 'inputLabelClass()' => ['form-check-label'], ], diff --git a/config/theme-bootstrap5-vertical.php b/config/theme-bootstrap5-vertical.php index 3829fb36..4bfc3019 100644 --- a/config/theme-bootstrap5-vertical.php +++ b/config/theme-bootstrap5-vertical.php @@ -5,6 +5,7 @@ use Yiisoft\Form\Field\Button; use Yiisoft\Form\Field\ButtonGroup; use Yiisoft\Form\Field\Checkbox; +use Yiisoft\Form\Field\CheckboxLabelPlacement; use Yiisoft\Form\Field\CheckboxList; use Yiisoft\Form\Field\ErrorSummary; use Yiisoft\Form\Field\RadioList; @@ -24,8 +25,8 @@ 'inputInvalidClass' => 'is-invalid', 'fieldConfigs' => [ Checkbox::class => [ - 'inputContainerTag()' => ['div'], - 'addInputContainerClass()' => ['form-check'], + 'labelPlacement()' => [CheckboxLabelPlacement::SIDE], + 'addContainerClass()' => ['form-check'], 'inputClass()' => ['form-check-input'], 'inputLabelClass()' => ['form-check-label'], ], From 0cca3b9cf2a491532331fdb5decf65b76e8a8ac3 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Thu, 3 Oct 2024 15:36:22 +0300 Subject: [PATCH 6/6] kill mutants --- tests/Field/CheckboxTest.php | 136 +++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/tests/Field/CheckboxTest.php b/tests/Field/CheckboxTest.php index 81e1493d..4b7f575d 100644 --- a/tests/Field/CheckboxTest.php +++ b/tests/Field/CheckboxTest.php @@ -702,6 +702,142 @@ public function testLabelPlacement(string $expected, CheckboxLabelPlacement $pla $this->assertSame($expected, $result); } + public static function dataLabelPlacementWithInputLabel(): iterable + { + yield 'default' => [ + << + + Moscow + + HTML, + CheckboxLabelPlacement::DEFAULT, + ]; + yield 'wrap' => [ + << + + + HTML, + CheckboxLabelPlacement::WRAP, + ]; + yield 'side' => [ + << + + + HTML, + CheckboxLabelPlacement::SIDE, + ]; + } + + #[DataProvider('dataLabelPlacementWithInputLabel')] + public function testLabelPlacementWithInputLabel(string $expected, CheckboxLabelPlacement $placement): void + { + $inputData = new InputData('city', label: 'Voronezh'); + + $result = Checkbox::widget() + ->inputData($inputData) + ->inputLabel('Moscow') + ->inputId('UID') + ->uncheckValue(null) + ->labelPlacement($placement) + ->render(); + + $this->assertSame($expected, $result); + } + + public static function dataLabelPlacementWithLabel(): iterable + { + yield 'default' => [ + << + + + + HTML, + CheckboxLabelPlacement::DEFAULT, + ]; + yield 'wrap' => [ + << + + + HTML, + CheckboxLabelPlacement::WRAP, + ]; + yield 'side' => [ + << + + + HTML, + CheckboxLabelPlacement::SIDE, + ]; + } + + #[DataProvider('dataLabelPlacementWithLabel')] + public function testLabelPlacementWithLabel(string $expected, CheckboxLabelPlacement $placement): void + { + $inputData = new InputData('city', label: 'Voronezh'); + + $result = Checkbox::widget() + ->inputData($inputData) + ->label('Moscow') + ->inputId('UID') + ->uncheckValue(null) + ->labelPlacement($placement) + ->render(); + + $this->assertSame($expected, $result); + } + + public static function dataLabelPlacementWithLabelAndInputLabel(): iterable + { + yield 'default' => [ + << + + Moscow + + HTML, + CheckboxLabelPlacement::DEFAULT, + ]; + yield 'wrap' => [ + << + + + HTML, + CheckboxLabelPlacement::WRAP, + ]; + yield 'side' => [ + << + + + HTML, + CheckboxLabelPlacement::SIDE, + ]; + } + + #[DataProvider('dataLabelPlacementWithLabelAndInputLabel')] + public function testLabelPlacementWithLabelAndInputLabel(string $expected, CheckboxLabelPlacement $placement): void + { + $inputData = new InputData('city', label: 'Voronezh'); + + $result = Checkbox::widget() + ->inputData($inputData) + ->inputLabel('Moscow') + ->label('Vladivostok') + ->inputId('UID') + ->uncheckValue(null) + ->labelPlacement($placement) + ->render(); + + $this->assertSame($expected, $result); + } + public function testImmutability(): void { $widget = Checkbox::widget();