Skip to content
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: 3 additions & 1 deletion src/Element.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@

use Stringable;

class Element implements Stringable
use Eightfold\HtmlBuilder\PropertyInterface;

class Element implements Stringable, PropertyInterface
{
/**
* The following attributes will be placed in this order if used in an
Expand Down
84 changes: 66 additions & 18 deletions src/Forms/Select.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,28 @@
use Stringable;

use Eightfold\HtmlBuilder\Element;
use Eightfold\HtmlBuilder\PropertyInterface;

use Eightfold\HtmlBuilder\Forms\SelectType;

class Select implements Stringable
{
// phpcs:disable
private string $warningId {
get => $this->name . '-warning';
}
// phpcs:enable

/**
* @var string[]
*/
private array $wrapperProperties = [];

/**
* @var string[]
*/
private array $labelProperties = [];

private SelectType $type = SelectType::Dropdown;

/**
Expand All @@ -26,9 +38,10 @@ public static function create(
string|Stringable $label,
string|Stringable $name,
array $options,
string|array $selected = []
string|array $selected = [],
string|PropertyInterface $warningMessage = ''
): self {
return new self($label, $name, $options, $selected);
return new self($label, $name, $options, $selected, $warningMessage);
}

/**
Expand All @@ -39,7 +52,8 @@ final private function __construct(
private readonly string|Stringable $label,
private readonly string|Stringable $name,
private readonly array $options,
private string|array $selected = []
private readonly string|array $selected = [],
private readonly string|PropertyInterface $warningMessage = ''
) {
}

Expand All @@ -49,6 +63,12 @@ public function wrapperProps(string ...$properties): self
return $this;
}

public function labelProps(string ...$properties): self
{
$this->labelProperties = $properties;
return $this;
}

public function dropdown(): self
{
$this->type = SelectType::Dropdown;
Expand All @@ -69,7 +89,7 @@ public function checkbox(): self

private function hasSelected(): bool
{
$selected = $this->selected();
$selected = self::selected();
if (is_string($selected) and strlen($selected) > 0) {
return true;

Expand Down Expand Up @@ -100,22 +120,37 @@ private function selected(): string|array

private function isSelected(string $value): bool
{
if ($this->hasSelected() === false) {
if (self::hasSelected() === false) {
return false;
}

if (is_array($this->selected())) {
return in_array($value, $this->selected());
if (is_array(self::selected())) {
return in_array($value, self::selected());
}
return $value === self::selected();
}

private function warningMessage(): string|PropertyInterface
{
if ($this->warningMessage === '') {
return '';
}

if (is_string($this->warningMessage)) {
return Element::p($this->warningMessage)->props(
'id ' . $this->warningId
);
}
return $value === $this->selected();

return $this->warningMessage->prop('id ' . $this->warningId);
}

public function __toString(): string
{
if ($this->type === SelectType::Dropdown) {
return (string) $this->selectDropdown();
return (string) self::selectDropdown();
}
return (string) $this->selectOther();
return (string) self::selectOther();
}

private function selectDropdown(): Element
Expand All @@ -124,19 +159,26 @@ private function selectDropdown(): Element
foreach ($this->options as $value => $content) {
$value = (string) $value;
$option = Element::option($content)->props('value ' . $value);
if ($this->isSelected($value)) {
if (self::isSelected($value)) {
$option = $option->prop('selected selected');
}
$elements[] = $option;
}

$input = Element::select(
...$elements
)->props('id ' . $this->name, 'name ' . $this->name);
if (self::warningMessage() !== '') {
$input = $input->prop('aria-invalid true');
$input = $input->prop('aria-describedby ' . $this->warningId);
}

return Element::div(
Element::label(
$this->label
)->props('for ' . $this->name),
Element::select(
...$elements
)->props('id ' . $this->name, 'name ' . $this->name)
)->props('for ' . $this->name, ...$this->labelProperties),
$input,
self::warningMessage()
)->props(...$this->wrapperProperties);
}

Expand All @@ -156,13 +198,19 @@ private function selectOther(): Element
'type ' . $type,
'value ' . $value
);
if ($this->isSelected($value)) {

if (self::isSelected($value)) {
$input = $input->prop('checked checked');
}
$elements[] = Element::div($input, $label);

if (self::warningMessage() !== '') {
$input = $input->prop('aria-invalid true');
$input = $input->prop('aria-describedby ' . $this->warningId);
}
$elements[] = Element::div($input, $label, self::warningMessage());
}
return Element::fieldset(
Element::legend($this->label),
Element::legend($this->label)->props(...$this->labelProperties),
...$elements
);
}
Expand Down
11 changes: 11 additions & 0 deletions src/PropertyInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);

namespace Eightfold\HtmlBuilder;

interface PropertyInterface
{
public function props(string ...$properties): static;

public function prop(string $prop): static;
}
117 changes: 105 additions & 12 deletions tests/Forms/SelectTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,101 @@

use Eightfold\HtmlBuilder\Forms\Select;

use Eightfold\HtmlBuilder\Element;

class SelectTest extends TestCase
{
#[Test]
public function warning_message_can_be_property_interface(): void // phpcs: ignore
{
$expected = <<<html
<div><label for="select">Select</label><select id="select" name="select" aria-describedby="select-warning" aria-invalid="true"><option value="value">display</option><option value="value2">display2</option></select><ul id="select-warning"><li>Invalid value</li></ul></div>
html;

$result = (string) Select::create(
label: 'Select',
name: 'select',
options: [
'value' => 'display',
'value2' => 'display2'
],
warningMessage: Element::ul(
Element::li('Invalid value')
)
);

parent::assertSame($expected, $result);
}

#[Test]
public function warning_message_can_be_string(): void // phpcs: ignore
{
$expected = <<<html
<div><label for="select">Select</label><select id="select" name="select" aria-describedby="select-warning" aria-invalid="true"><option value="value">display</option><option value="value2">display2</option></select><p id="select-warning">Invalid value</p></div>
html;

$result = (string) Select::create(
label: 'Select',
name: 'select',
options: [
'value' => 'display',
'value2' => 'display2'
],
warningMessage: 'Invalid value'
);

parent::assertSame($expected, $result);
}

#[Test]
public function can_add_props_to_label(): void // phpcs: ignore
{
$expected = <<<html
<div><label is="select-dropdown" for="select">Select</label><select id="select" name="select"><option value="value">display</option><option value="value2">display2</option></select></div>
html;

$result = (string) Select::create(
label: 'Select',
name: 'select',
options: [
'value' => 'display',
'value2' => 'display2'
]
)->labelProps('is select-dropdown');

parent::assertSame($expected, $result);

$expected = <<<html
<fieldset><legend is="checkbox">Select</legend><div><input id="select-value" name="select[]" type="checkbox" value="value"><label for="select-value">display</label></div><div><input id="select-value2" name="select[]" type="checkbox" value="value2"><label for="select-value2">display2</label></div></fieldset>
html;

$result = (string) Select::create(
label: 'Select',
name: 'select',
options: [
'value' => 'display',
'value2' => 'display2'
]
)->checkbox()->labelProps('is checkbox');

parent::assertSame($expected, $result);

$expected = <<<html
<fieldset><legend is="radio">Select</legend><div><input id="select-value" name="select" type="radio" value="value"><label for="select-value">display</label></div><div><input id="select-value2" name="select" type="radio" value="value2"><label for="select-value2">display2</label></div></fieldset>
html;

$result = (string) Select::create(
label: 'Select',
name: 'select',
options: [
'value' => 'display',
'value2' => 'display2'
]
)->radio()->labelProps('is radio');

parent::assertSame($expected, $result);
}

#[Test]
public function can_be_checkboxes(): void // phpcs: ignore
{
Expand All @@ -18,16 +111,16 @@ public function can_be_checkboxes(): void // phpcs: ignore
html;

$result = (string) Select::create(
'Select your option',
'select',
[
label: 'Select your option',
name: 'select',
options: [
'value' => 'display',
'value2' => 'display2'
],
['value', 'value2']
selected: ['value', 'value2']
)->checkbox();

$this->assertSame($expected, $result);
parent::assertSame($expected, $result);
}

#[Test]
Expand All @@ -46,7 +139,7 @@ public function can_be_radio_buttons(): void // phpcs: ignore
'value'
)->radio();

$this->assertSame($expected, $result);
parent::assertSame($expected, $result);
}

#[Test]
Expand All @@ -64,7 +157,7 @@ public function dropdown_mode_is_explicit(): void // phpcs: ignore
]
)->radio()->dropdown();

$this->assertSame($expected, $result);
parent::assertSame($expected, $result);
}

#[Test]
Expand All @@ -83,7 +176,7 @@ public function checkbox_mode_is_explicit(): void // phpcs: ignore
'value'
)->radio()->checkbox();

$this->assertSame($expected, $result);
parent::assertSame($expected, $result);
}

#[Test]
Expand All @@ -101,7 +194,7 @@ public function can_add_properties_to_wrapper(): void // phpcs: ignore
]
)->wrapperProps('id some-id', 'class some-token');

$this->assertSame($expected, $result);
parent::assertSame($expected, $result);
}

#[Test]
Expand All @@ -121,7 +214,7 @@ public function can_preselect_option(): void // phpcs:ignore
'value2'
);

$this->assertSame($expected, $result);
parent::assertSame($expected, $result);
}

#[Test]
Expand All @@ -139,7 +232,7 @@ public function is_expected_base(): void // phpcs:ignore
]
);

$this->assertSame($expected, $result);
parent::assertSame($expected, $result);
}

#[Test]
Expand All @@ -158,6 +251,6 @@ public function error_is_selected_value_always_string(): void // phpcs:ignore
]
);

$this->assertSame($expected, $result);
parent::assertSame($expected, $result);
}
}
Loading