Skip to content

Commit 82c7dfb

Browse files
authored
Merge pull request #13 from 8fold/forms
add: Select dropdown, radio, checkbox
2 parents 2719a5c + 56b1895 commit 82c7dfb

File tree

2 files changed

+291
-0
lines changed

2 files changed

+291
-0
lines changed

src/Forms/Select.php

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Eightfold\HTMLBuilder\Forms;
5+
6+
use Stringable;
7+
8+
use Eightfold\HTMLBuilder\Element;
9+
10+
class Select implements Stringable
11+
{
12+
/**
13+
* @var string[]
14+
*/
15+
private array $wrapperProperties = [];
16+
17+
private bool $dropdown = true;
18+
19+
private bool $checkbox = false;
20+
21+
/**
22+
* @param array<string, string> $options
23+
* @param string|string[] $selected
24+
*/
25+
public static function create(
26+
string|Stringable $label,
27+
string|Stringable $name,
28+
array $options,
29+
string|array $selected = []
30+
): self {
31+
return new self($label, $name, $options, $selected);
32+
}
33+
34+
/**
35+
* @param array<string, string> $options
36+
* @param string|string[] $selected
37+
*/
38+
final private function __construct(
39+
private readonly string|Stringable $label,
40+
private readonly string|Stringable $name,
41+
private readonly array $options,
42+
private string|array $selected = []
43+
) {
44+
}
45+
46+
public function wrapperProps(string ...$propperties): self
47+
{
48+
$this->wrapperProperties = $propperties;
49+
return $this;
50+
}
51+
52+
public function dropdown(): self
53+
{
54+
$this->dropdown = true;
55+
$this->checkbox = false;
56+
return $this;
57+
}
58+
59+
public function radio(): self
60+
{
61+
$this->dropdown = false;
62+
$this->checkbox = false;
63+
return $this;
64+
}
65+
66+
public function checkbox(): self
67+
{
68+
$this->checkbox = true;
69+
$this->dropdown = false;
70+
return $this;
71+
}
72+
73+
private function hasSelected(): bool
74+
{
75+
$selected = $this->selected();
76+
if (is_string($selected) and strlen($selected) > 0) {
77+
return true;
78+
79+
} elseif (is_array($selected) and count($selected) > 0) {
80+
return true;
81+
82+
}
83+
return false;
84+
}
85+
86+
/**
87+
* @return string|string[]
88+
*/
89+
private function selected(): string|array
90+
{
91+
if ($this->checkbox) {
92+
if (is_array($this->selected)) {
93+
return $this->selected;
94+
}
95+
return [$this->selected];
96+
}
97+
98+
if (is_array($this->selected) and count($this->selected) > 0) {
99+
return $this->selected[0];
100+
}
101+
return $this->selected;
102+
}
103+
104+
private function isSelected(string $value): bool
105+
{
106+
if ($this->hasSelected() === false) {
107+
return false;
108+
}
109+
110+
if (is_array($this->selected())) {
111+
return in_array($value, $this->selected());
112+
}
113+
return $value === $this->selected();
114+
}
115+
116+
public function __toString(): string
117+
{
118+
if ($this->dropdown) {
119+
return (string) $this->selectDropdown();
120+
}
121+
return (string) $this->selectOther();
122+
}
123+
124+
private function selectDropdown(): Element
125+
{
126+
$elements = [];
127+
foreach ($this->options as $value => $content) {
128+
$option = Element::option($content)->props('value ' . $value);
129+
if ($this->isSelected($value)) {
130+
$option = $option->prop('selected selected');
131+
}
132+
$elements[] = $option;
133+
}
134+
135+
return Element::div(
136+
Element::label(
137+
$this->label
138+
)->props('for ' . $this->name),
139+
Element::select(
140+
...$elements
141+
)->props('id ' . $this->name, 'name ' . $this->name)
142+
)->props(...$this->wrapperProperties);
143+
}
144+
145+
private function selectOther(): Element
146+
{
147+
$elements = [];
148+
$type = 'radio';
149+
if ($this->checkbox) {
150+
$type = 'checkbox';
151+
}
152+
foreach ($this->options as $value => $content) {
153+
$id = $this->name . '-' . $value;
154+
$label = Element::label($content)->props('for ' . $id);
155+
$input = Element::input()->omitEndTag()->props(
156+
'id ' . $id,
157+
'type ' . $type,
158+
'value ' . $value
159+
);
160+
if ($this->isSelected($value)) {
161+
$input = $input->prop('checked checked');
162+
}
163+
$elements[] = Element::div($input, $label);
164+
}
165+
return Element::fieldset(
166+
Element::legend($this->label),
167+
...$elements
168+
);
169+
}
170+
}

tests/Forms/SelectTest.php

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Eightfold\HTMLBuilder\Tests;
5+
6+
use PHPUnit\Framework\TestCase;
7+
8+
// use Eightfold\HTMLBuilder\Tests\Extensions\ElementExtension;
9+
10+
// use Eightfold\XMLBuilder\Comment;
11+
12+
// use Eightfold\HTMLBuilder\Document;
13+
use Eightfold\HTMLBuilder\Forms\Select;
14+
15+
class SelectTest extends TestCase
16+
{
17+
/**
18+
* @test
19+
*/
20+
public function can_be_checkboxes(): void // phpcs: ignore
21+
{
22+
$expected = <<<html
23+
<fieldset><legend>Select your option</legend><div><input id="select-value" type="checkbox" value="value" checked><label for="select-value">display</label></div><div><input id="select-value2" type="checkbox" value="value2" checked><label for="select-value2">display2</label></div></fieldset>
24+
html;
25+
26+
$result = (string) Select::create(
27+
'Select your option',
28+
'select',
29+
[
30+
'value' => 'display',
31+
'value2' => 'display2'
32+
],
33+
['value', 'value2']
34+
)->checkbox();
35+
36+
$this->assertSame($expected, $result);
37+
}
38+
39+
/**
40+
* @test
41+
*/
42+
public function can_be_radio_buttons(): void // phpcs: ignore
43+
{
44+
$expected = <<<html
45+
<fieldset><legend>Select your option</legend><div><input id="select-value" type="radio" value="value" checked><label for="select-value">display</label></div></fieldset>
46+
html;
47+
48+
$result = (string) Select::create(
49+
'Select your option',
50+
'select',
51+
[
52+
'value' => 'display'
53+
],
54+
'value'
55+
)->radio();
56+
57+
$this->assertSame($expected, $result);
58+
}
59+
60+
/**
61+
* @test
62+
*/
63+
public function can_add_properties_to_wrapper(): void // phpcs: ignore
64+
{
65+
$expected = <<<html
66+
<div id="some-id" class="some-token"><label for="select">Select your option</label><select id="select" name="select"><option value="value">display</option></select></div>
67+
html;
68+
69+
$result = (string) Select::create(
70+
'Select your option',
71+
'select',
72+
[
73+
'value' => 'display'
74+
]
75+
)->wrapperProps('id some-id', 'class some-token');
76+
77+
$this->assertSame($expected, $result);
78+
}
79+
80+
/**
81+
* @test
82+
*/
83+
public function can_preselect_option(): void // phpcs:ignore
84+
{
85+
$expected = <<<html
86+
<div><label for="select">Select your option</label><select id="select" name="select"><option value="value">display</option><option value="value2" selected>display2</option></select></div>
87+
html;
88+
89+
$result = (string) Select::create(
90+
'Select your option',
91+
'select',
92+
[
93+
'value' => 'display',
94+
'value2' => 'display2'
95+
],
96+
'value2'
97+
);
98+
99+
$this->assertSame($expected, $result);
100+
}
101+
102+
/**
103+
* @test
104+
*/
105+
public function is_expected_base(): void // phpcs:ignore
106+
{
107+
$expected = <<<html
108+
<div><label for="select">Select your option</label><select id="select" name="select"><option value="value">display</option></select></div>
109+
html;
110+
111+
$result = (string) Select::create(
112+
'Select your option',
113+
'select',
114+
[
115+
'value' => 'display'
116+
]
117+
);
118+
119+
$this->assertSame($expected, $result);
120+
}
121+
}

0 commit comments

Comments
 (0)