Skip to content

Commit

Permalink
Fix bug in Checkbox::class, document all cases (#95)
Browse files Browse the repository at this point in the history
  • Loading branch information
terabytesoftw committed Oct 7, 2021
1 parent a00c7b7 commit 4019bc8
Show file tree
Hide file tree
Showing 5 changed files with 246 additions and 38 deletions.
6 changes: 6 additions & 0 deletions README.md
Expand Up @@ -123,6 +123,12 @@ class LoginForm extends FormModel
}
```

## Widgets usage

The following documentation describes how to use widgets with PHP:

- [Checkbox](docs/checkbox.md)

### Unit testing

The package is tested with [PHPUnit](https://phpunit.de/). To run tests:
Expand Down
196 changes: 196 additions & 0 deletions docs/checkbox.md
@@ -0,0 +1,196 @@
# Checkbox widget

[Checkbox](https://www.w3.org/TR/2012/WD-html-markup-20120329/input.checkbox.html#input.checkbox) is an input element with a type attribute whose value is "checkbox" represents a state or option that can be toggled.

## Usage

The `Checkbox::class` widget is designed to return the status of the checkbox. Generally it returns two values, by default it is `0` when no value is selected, and` 1` otherwise.

You can also configure the widget to return the value only when the check box is selected.

### Example Checkbox with default values `0` and `1`:

TestForm form:

```php
<?php

declare(strict_types=1);

namespace App\Form;

use Yiisoft\Form\FormModel;

final class TestForm extends FormModel
{
public bool $active = false;
}
```

Widgets view:

```php
<?php

declare(strict_types=1);

use Yiisoft\Form\Widget\Checkbox;
use Yiisoft\Form\Widget\Field;
use Yiisoft\Form\Widget\Form;
use Yiisoft\Form\Widget\Label;
?>

<?= Form::widget()->action('widgets')->csrf($csrf)->begin() ?>
<?= Checkbox::widget()->config($data, 'active', [])->render() ?>
<hr class="mt-3">
<?= Field::widget()->submitButton(['class' => 'button is-block is-info is-fullwidth', 'value' => 'Save']); ?>
<?= Form::end() ?>
```

### Code generated by `Checkbox::class` Widget

```html
<form action="widgets" method="POST" _csrf="H4r91n-qkv8GzgEU8E3hhn2vSSu5jnkguqemRBysYa1OwJWHR8Txmk6kUSWGHdfnT54zYPXoDkqN1v4CefZR2w==">
<input type="hidden" name="_csrf" value="H4r91n-qkv8GzgEU8E3hhn2vSSu5jnkguqemRBysYa1OwJWHR8Txmk6kUSWGHdfnT54zYPXoDkqN1v4CefZR2w==">
<input type="hidden" name="TestForm[active]" value="0">
<label><input type="checkbox" id="testform-active" name="TestForm[active]" value="1">Active</label>
<hr class="mt-3">
<div>
<input type="submit" id="submit-181408062514001" class="button is-block is-info is-fullwidth" name="submit-181408062514001" value="Save">
</div>
</form>
```

As we can see in the code we have the value `0` for the hidden input and `1` for the input checkbox tag, correctly.

### Example Checkbox with default values `1` when the checkbox is checked:

TestForm form:

```php
<?php

declare(strict_types=1);

namespace App\Form;

use Yiisoft\Form\FormModel;

final class TestForm extends FormModel
{
public bool $active = false;
}
```

Widgets view:

```php
<?php

declare(strict_types=1);

use Yiisoft\Form\Widget\Checkbox;
use Yiisoft\Form\Widget\Field;
use Yiisoft\Form\Widget\Form;
use Yiisoft\Form\Widget\Label;
?>

<?= Form::widget()->action('widgets')->csrf($csrf)->begin() ?>
<?= Checkbox::widget()->config($data, 'active', ['forceUncheckedValue' => null])->render() ?>
<hr class="mt-3">
<?= Field::widget()->submitButton(['class' => 'button is-block is-info is-fullwidth', 'value' => 'Save']); ?>
<?= Form::end() ?>
```

### Code generated by `Checkbox::class` Widget

```html
<form action="widgets" method="POST" _csrf="Oq_3knkum-2MVDBcu5v_Zbdv4j4NQWKyueB0wrSmExhr5Z_DQUD4iMQ-YG3Ny8kEhV6YdUEnFdiOkSyE0fwjbg==">
<input type="hidden" name="_csrf" value="Oq_3knkum-2MVDBcu5v_Zbdv4j4NQWKyueB0wrSmExhr5Z_DQUD4iMQ-YG3Ny8kEhV6YdUEnFdiOkSyE0fwjbg==">
<label><input type="checkbox" id="testform-active" name="TestForm[active]" value="1"> Active</label>
<hr class="mt-3">
<div>
<input type="submit" id="submit-186398933261001" class="button is-block is-info is-fullwidth" name="submit-186398933261001" value="Save">
</div>
</form>
```

As we can see there is no hidden input, only `1` for the input checkbox tag, correctly.

### Example Checkbox with values `inactive` and `active` values:

TestForm form:

```php
<?php

declare(strict_types=1);

namespace App\Form;

use Yiisoft\Form\FormModel;

final class TestForm extends FormModel
{
public string $active = 'inactive';
}
```

Widgets view:

```php
<?php

declare(strict_types=1);

use Yiisoft\Form\Widget\Checkbox;
use Yiisoft\Form\Widget\Field;
use Yiisoft\Form\Widget\Form;
use Yiisoft\Form\Widget\Label;
?>

<?= Form::widget()->action('widgets')->csrf($csrf)->begin() ?>
<?= Checkbox::widget()->config($data, 'active', ['forceUncheckedValue' => 'inactive', 'value' => 'active'])->render() ?>
<hr class="mt-3">
<?= Field::widget()->submitButton(['class' => 'button is-block is-info is-fullwidth', 'value' => 'Save']); ?>
<?= Form::end() ?>
```

### Code generated by `Checkbox::class` Widget

```html
<form action="widgets" method="POST" _csrf="ifAHTGhruJd9LvvzfoU3xmftdLx3ReD89w21FZpDXajYum8dUAXb8jVEq8II1QGnVdwO9zsjl5bAfO1T_xlt3g==">
<input type="hidden" name="_csrf" value="ifAHTGhruJd9LvvzfoU3xmftdLx3ReD89w21FZpDXajYum8dUAXb8jVEq8II1QGnVdwO9zsjl5bAfO1T_xlt3g==">
<input type="hidden" name="TestForm[active]" value="inactive">
<label><input type="checkbox" id="testform-active" name="TestForm[active]" value="active"> Active</label>
<hr class="mt-3">
<div>
<input type="submit" id="submit-171128066299001" class="button is-block is-info is-fullwidth" name="submit-171128066299001" value="Save">
</div>
</form>
```

As we can see in the code we have the value `inactive` for the hidden input and `active` for the input checkbox tag, correctly.

### `Checkbox::class` methods:

Method | Description | Default
-------|-------------|---------
`enclosedByLabel(bool $value = true)` | If the widget should be enclosed by label. | `true`
`label(string $value)` | The label text. | `''`
`labelAttributes(array $attributes = [])` | The HTML attributes for the label tag. | `[]`

### Common methods:

Method | Description | Default
-------|-------------|---------
`charset(string $value)` | Sets the charset attribute | `UTF-8`
`config(FormModelInterface $formModel, string $attribute, array $attributes = [])` | Configures the widget. |
`autofocus(bool $value = true)` | Sets the autofocus attribute | `false`
`disabled(bool $value = true)` | Sets the disabled attribute | `false`
`form(string $value)` | Sets the form attribute | ``
`id(string $value)` | Sets the id attribute | `''`
`required(bool $value = true)` | Sets the required attribute | `false`
`readonly()` | Sets the readonly attribute | `false`
`tabIndex(int $value = 0)` | Sets the tabindex attribute | `0`

17 changes: 10 additions & 7 deletions src/Widget/Checkbox.php
Expand Up @@ -90,27 +90,30 @@ protected function run(): string

$checkbox = CheckboxTag::tag();

/** @var bool|float|int|string|null */
$forceUncheckedValue = ArrayHelper::remove($new->attributes, 'forceUncheckedValue');

$value = HtmlForm::getAttributeValue($new->getFormModel(), $new->attribute);

if (is_iterable($value) || is_object($value)) {
throw new InvalidArgumentException('Checkbox widget requires a bool|float|int|string|null value.');
throw new InvalidArgumentException('Checkbox widget value can not be an iterable or an object.');
}

$new->attributes['value'] = array_key_exists('value', $new->attributes) ? $new->attributes['value'] : '1';

/** @var bool|float|int|string|null */
$forceUncheckedValue = ArrayHelper::remove($new->attributes, 'forceUncheckedValue', '0');

unset($new->attributes['forceUncheckedValue']);

if ($new->enclosedByLabel === true) {
$label = $new->label !== '' ? $new->label : HtmlForm::getAttributeLabel($new->getFormModel(), $new->attribute);
$checkbox = $checkbox->label($label, $new->labelAttributes);
}

return $checkbox
->checked("$value" === "{$new->attributes['value']}")
->attributes($new->attributes)
->checked((bool) $value)
->id($new->getId())
->name(HtmlForm::getInputName($this->getFormModel(), $this->attribute))
->name(HtmlForm::getInputName($new->getFormModel(), $new->attribute))
->uncheckValue($forceUncheckedValue)
->value((int) $value)
->render();
}
}
29 changes: 16 additions & 13 deletions tests/Widget/CheckboxTest.php
Expand Up @@ -17,16 +17,19 @@ final class CheckboxTest extends TestCase

public function testEnclosedByLabel(): void
{
$expected = <<<'HTML'
<input type="hidden" name="TypeForm[bool]" value="0"><input type="checkbox" id="typeform-bool" name="TypeForm[bool]" value="1">
HTML;
$this->assertSame(
'<input type="checkbox" id="typeform-bool" name="TypeForm[bool]" value="0">',
$expected,
Checkbox::widget()->config($this->formModel, 'bool')->enclosedByLabel(false)->render(),
);
}

public function testForceUncheckedValue(): void
{
$expected = <<<'HTML'
<input type="hidden" name="TypeForm[bool]" value="0"><label><input type="checkbox" id="typeform-bool" name="TypeForm[bool]" value="0"> Bool</label>
<input type="hidden" name="TypeForm[bool]" value="0"><label><input type="checkbox" id="typeform-bool" name="TypeForm[bool]" value="1"> Bool</label>
HTML;
$this->assertSame(
$expected,
Expand All @@ -37,7 +40,7 @@ public function testForceUncheckedValue(): void
public function testForm(): void
{
$expected = <<<'HTML'
<label><input type="checkbox" id="typeform-bool" name="TypeForm[bool]" value="0" form="form-id"> Bool</label>
<input type="hidden" name="TypeForm[bool]" value="0" form="form-id"><label><input type="checkbox" id="typeform-bool" name="TypeForm[bool]" value="1" form="form-id"> Bool</label>
HTML;
$this->assertSame(
$expected,
Expand All @@ -58,7 +61,7 @@ public function testLabelWithLabelAttributes(): void
{
$this->formModel->setAttribute('bool', true);
$expected = <<<'HTML'
<label class="test-class"><input type="checkbox" id="typeform-bool" name="TypeForm[bool]" value="1" checked> test-text-label</label>
<input type="hidden" name="TypeForm[bool]" value="0"><label class="test-class"><input type="checkbox" id="typeform-bool" name="TypeForm[bool]" value="1" checked> test-text-label</label>
HTML;
$html = Checkbox::widget()
->config($this->formModel, 'bool')
Expand All @@ -72,7 +75,7 @@ public function testRender(): void
{
$this->formModel->setAttribute('bool', true);
$expected = <<<'HTML'
<label><input type="checkbox" id="typeform-bool" name="TypeForm[bool]" value="1" checked> Bool</label>
<input type="hidden" name="TypeForm[bool]" value="0"><label><input type="checkbox" id="typeform-bool" name="TypeForm[bool]" value="1" checked> Bool</label>
HTML;
$this->assertSame($expected, Checkbox::widget()->config($this->formModel, 'bool')->render());
}
Expand All @@ -82,57 +85,57 @@ public function testValues(): void
// value bool false
$this->formModel->setAttribute('bool', false);
$expected = <<<'HTML'
<label><input type="checkbox" id="typeform-bool" name="TypeForm[bool]" value="0"> Bool</label>
<input type="hidden" name="TypeForm[bool]" value="0"><label><input type="checkbox" id="typeform-bool" name="TypeForm[bool]" value="1"> Bool</label>
HTML;
$this->assertSame($expected, Checkbox::widget()->config($this->formModel, 'bool')->render());

// value bool true
$this->formModel->setAttribute('bool', true);
$expected = <<<'HTML'
<label><input type="checkbox" id="typeform-bool" name="TypeForm[bool]" value="1" checked> Bool</label>
<input type="hidden" name="TypeForm[bool]" value="0"><label><input type="checkbox" id="typeform-bool" name="TypeForm[bool]" value="1" checked> Bool</label>
HTML;
$this->assertSame($expected, Checkbox::widget()->config($this->formModel, 'bool')->render());

// value int 0
$this->formModel->setAttribute('int', 0);
$expected = <<<'HTML'
<label><input type="checkbox" id="typeform-int" name="TypeForm[int]" value="0"> Int</label>
<input type="hidden" name="TypeForm[int]" value="0"><label><input type="checkbox" id="typeform-int" name="TypeForm[int]" value="1"> Int</label>
HTML;
$this->assertSame($expected, Checkbox::widget()->config($this->formModel, 'int')->render());

// value int 1
$this->formModel->setAttribute('int', 1);
$expected = <<<'HTML'
<label><input type="checkbox" id="typeform-int" name="TypeForm[int]" value="1" checked> Int</label>
<input type="hidden" name="TypeForm[int]" value="0"><label><input type="checkbox" id="typeform-int" name="TypeForm[int]" value="1" checked> Int</label>
HTML;
$this->assertSame($expected, Checkbox::widget()->config($this->formModel, 'int')->render());

// value string '0'
$this->formModel->setAttribute('string', '0');
$expected = <<<'HTML'
<label><input type="checkbox" id="typeform-string" name="TypeForm[string]" value="0"> String</label>
<input type="hidden" name="TypeForm[string]" value="0"><label><input type="checkbox" id="typeform-string" name="TypeForm[string]" value="1"> String</label>
HTML;
$this->assertSame($expected, Checkbox::widget()->config($this->formModel, 'string')->render());

// value string '1'
$this->formModel->setAttribute('string', '1');
$expected = <<<'HTML'
<label><input type="checkbox" id="typeform-string" name="TypeForm[string]" value="1" checked> String</label>
<input type="hidden" name="TypeForm[string]" value="0"><label><input type="checkbox" id="typeform-string" name="TypeForm[string]" value="1" checked> String</label>
HTML;
$this->assertSame($expected, Checkbox::widget()->config($this->formModel, 'string')->render());

// value null
$this->formModel->setAttribute('toNull', null);
$expected = <<<'HTML'
<label><input type="checkbox" id="typeform-tonull" name="TypeForm[toNull]" value="0"> To Null</label>
<input type="hidden" name="TypeForm[toNull]" value="0"><label><input type="checkbox" id="typeform-tonull" name="TypeForm[toNull]" value="1"> To Null</label>
HTML;
$this->assertSame($expected, Checkbox::widget()->config($this->formModel, 'toNull')->render());
}

public function testValueException(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Checkbox widget requires a bool|float|int|string|null value.');
$this->expectExceptionMessage('Checkbox widget value can not be an iterable or an object.');
$html = Checkbox::widget()->config($this->formModel, 'array')->render();
}

Expand Down

0 comments on commit 4019bc8

Please sign in to comment.