Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Checkbox::labelPlacement() #364

Merged
merged 6 commits into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
5 changes: 3 additions & 2 deletions config/theme-bootstrap5-horizontal.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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'],
],
Expand Down
5 changes: 3 additions & 2 deletions config/theme-bootstrap5-vertical.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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'],
],
Expand Down
46 changes: 38 additions & 8 deletions src/Field/Checkbox.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
{
Expand All @@ -91,9 +94,19 @@ public function enclosedByLabel(bool $value): self
}

/**
* Label displayed next to the checkbox.
* Set label placement relative to checkbox input.
*
* When this option is specified, the checkbox will be enclosed by a label tag.
* @see CheckboxLabelPlacement
*/
public function labelPlacement(CheckboxLabelPlacement $placement): self
{
$new = clone $this;
$new->labelPlacement = $placement;
return $new;
}

/**
* Label displayed next to the checkbox.
*
* @link https://www.w3.org/TR/html52/sec-forms.html#the-label-element
*/
Expand Down Expand Up @@ -124,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.
*/
Expand All @@ -136,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.
*/
Expand All @@ -148,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.
*/
Expand Down Expand Up @@ -233,19 +246,26 @@ protected function generateInput(): string

$checkbox = Html::checkbox($this->getName(), $inputValue, $inputAttributes);

if ($this->enclosedByLabel) {
$labelPlacement = $this->getLabelPlacement();

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
->checked($inputValue === $value)
->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);
}

Expand All @@ -254,7 +274,7 @@ protected function generateInput(): string

protected function shouldHideLabel(): bool
{
return $this->enclosedByLabel;
return $this->getLabelPlacement() !== CheckboxLabelPlacement::DEFAULT;
}

private function prepareCheckboxValue(mixed $value): ?string
Expand Down Expand Up @@ -287,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;
}
}
26 changes: 26 additions & 0 deletions src/Field/CheckboxLabelPlacement.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

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;
}
182 changes: 182 additions & 0 deletions tests/Field/CheckboxTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -657,12 +658,193 @@ public function testInvalidClassesWithCustomError(): void
$this->assertSame($expected, $result);
}

public static function dataLabelPlacement(): iterable
{
yield 'default' => [
<<<HTML
<div>
<label for="UID">Voronezh</label>
<input type="checkbox" id="UID" name="city" value="1">
</div>
HTML,
CheckboxLabelPlacement::DEFAULT,
];
yield 'wrap' => [
<<<HTML
<div>
<label><input type="checkbox" id="UID" name="city" value="1"> Voronezh</label>
</div>
HTML,
CheckboxLabelPlacement::WRAP,
];
yield 'side' => [
<<<HTML
<div>
<input type="checkbox" id="UID" name="city" value="1"> <label for="UID">Voronezh</label>
</div>
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 static function dataLabelPlacementWithInputLabel(): iterable
{
yield 'default' => [
<<<HTML
<div>
<label for="UID">Voronezh</label>
<input type="checkbox" id="UID" name="city" value="1"> Moscow
</div>
HTML,
CheckboxLabelPlacement::DEFAULT,
];
yield 'wrap' => [
<<<HTML
<div>
<label><input type="checkbox" id="UID" name="city" value="1"> Moscow</label>
</div>
HTML,
CheckboxLabelPlacement::WRAP,
];
yield 'side' => [
<<<HTML
<div>
<input type="checkbox" id="UID" name="city" value="1"> <label for="UID">Moscow</label>
</div>
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
<div>
<label for="UID">Moscow</label>
<input type="checkbox" id="UID" name="city" value="1">
</div>
HTML,
CheckboxLabelPlacement::DEFAULT,
];
yield 'wrap' => [
<<<HTML
<div>
<label><input type="checkbox" id="UID" name="city" value="1"> Moscow</label>
</div>
HTML,
CheckboxLabelPlacement::WRAP,
];
yield 'side' => [
<<<HTML
<div>
<input type="checkbox" id="UID" name="city" value="1"> <label for="UID">Moscow</label>
</div>
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' => [
<<<HTML
<div>
<label for="UID">Vladivostok</label>
<input type="checkbox" id="UID" name="city" value="1"> Moscow
</div>
HTML,
CheckboxLabelPlacement::DEFAULT,
];
yield 'wrap' => [
<<<HTML
<div>
<label><input type="checkbox" id="UID" name="city" value="1"> Moscow</label>
</div>
HTML,
CheckboxLabelPlacement::WRAP,
];
yield 'side' => [
<<<HTML
<div>
<input type="checkbox" id="UID" name="city" value="1"> <label for="UID">Moscow</label>
</div>
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();

$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([]));
Expand Down